import itertools
import re
from collections.abc import Collection
from dataclasses import dataclass
from datetime import datetime
from typing import Any

from django.db.models import QuerySet
from django.http import HttpRequest, HttpResponse, HttpResponseNotFound
from django.shortcuts import render
from django.utils.timezone import now as timezone_now
from markupsafe import Markup

from corporate.lib.activity import (
    format_optional_datetime,
    make_table,
    realm_stats_link,
    user_activity_link,
)
from zerver.decorator import require_server_admin
from zerver.models import Realm, UserActivity
from zerver.models.users import UserProfile


@dataclass
class UserActivitySummary:
    user_name: str
    user_id: int
    user_type: str
    messages_sent: int
    last_heard_from: datetime | None
    last_message_sent: datetime | None
    bot_owner_id: int | None


def get_user_activity_records_for_realm(realm: str) -> QuerySet[UserActivity]:
    fields = [
        "user_profile__full_name",
        "user_profile__delivery_email",
        "user_profile__is_bot",
        "user_profile__bot_type",
        "query",
        "count",
        "last_visit",
    ]

    records = (
        UserActivity.objects.filter(
            user_profile__realm__string_id=realm,
            user_profile__is_active=True,
        )
        .order_by("user_profile__delivery_email", "-last_visit")
        .select_related("user_profile")
        .only(*fields)
    )
    return records


def get_user_activity_summary(records: Collection[UserActivity]) -> UserActivitySummary:
    if records:
        first_record = next(iter(records))
        name = first_record.user_profile.full_name
        user_profile_id = first_record.user_profile.id
        bot_owner_id = None
        if not first_record.user_profile.is_bot:
            user_type = "Human"
        else:
            assert first_record.user_profile.bot_type is not None
            bot_type = first_record.user_profile.bot_type
            user_type = UserProfile.BOT_TYPES[bot_type]
            if first_record.user_profile.bot_owner is not None:
                bot_owner_id = first_record.user_profile.bot_owner.id

    messages = 0
    heard_from: datetime | None = None
    last_sent: datetime | None = None

    for record in records:
        query = str(record.query)
        visit = record.last_visit

        if heard_from is None:
            heard_from = visit
        else:
            heard_from = max(visit, heard_from)

        if ("send_message" in query) or re.search(r"/api/.*/external/.*", query):
            messages += record.count
            if last_sent is None:
                last_sent = visit
            else:
                last_sent = max(visit, last_sent)

    return UserActivitySummary(
        user_name=name,
        user_id=user_profile_id,
        user_type=user_type,
        messages_sent=messages,
        last_heard_from=heard_from,
        last_message_sent=last_sent,
        bot_owner_id=bot_owner_id,
    )


def realm_user_summary_table(
    all_records: QuerySet[UserActivity], admin_emails: set[str], title: str, stats_link: Markup
) -> str:
    user_records: dict[str, UserActivitySummary] = {}

    def by_email(record: UserActivity) -> str:
        return record.user_profile.delivery_email

    for email, records in itertools.groupby(all_records, by_email):
        user_records[email] = get_user_activity_summary(list(records))

    def is_recent(val: datetime) -> bool:
        age = timezone_now() - val
        return age.total_seconds() < 5 * 60

    cols = [
        "Name",
        "Email",
        "User type",
        "Messages sent",
        "Last heard from (UTC)",
        "Last message sent (UTC)",
    ]

    rows = []
    for email, user_summary in user_records.items():
        email_link = user_activity_link(email, user_summary.user_id)
        if user_summary.bot_owner_id is not None:
            bot_owner_link = user_activity_link("", user_summary.bot_owner_id)
            user_name = user_summary.user_name + " " + bot_owner_link
            cells = [
                user_name,
                email_link,
                user_summary.user_type,
                user_summary.messages_sent,
            ]
        else:
            cells = [
                user_summary.user_name,
                email_link,
                user_summary.user_type,
                user_summary.messages_sent,
            ]
        cells.append(format_optional_datetime(user_summary.last_heard_from))
        cells.append(format_optional_datetime(user_summary.last_message_sent))

        row_class = ""
        if user_summary.last_heard_from and is_recent(user_summary.last_heard_from):
            row_class += " recently_active"
        if email in admin_emails:
            row_class += " admin"

        row = dict(cells=cells, row_class=row_class)
        rows.append(row)

    def by_last_heard_from(row: dict[str, Any]) -> str:
        return row["cells"][4]

    rows = sorted(rows, key=by_last_heard_from, reverse=True)
    content = make_table(title, cols, rows, title_link=stats_link, has_row_class=True)
    return content


@require_server_admin
def get_realm_activity(request: HttpRequest, realm_str: str) -> HttpResponse:
    try:
        admins = Realm.objects.get(string_id=realm_str).get_human_admin_users()
    except Realm.DoesNotExist:
        return HttpResponseNotFound()

    admin_emails = {admin.delivery_email for admin in admins}
    all_records = get_user_activity_records_for_realm(realm_str)
    realm_stats = realm_stats_link(realm_str)
    title = realm_str
    content = realm_user_summary_table(all_records, admin_emails, title, realm_stats)

    return render(
        request,
        "corporate/activity/activity.html",
        context=dict(
            data=content,
            title=title,
            is_home=False,
        ),
    )
