"""App Views"""

# Standard Library
from datetime import datetime
from datetime import timezone as dt_timezone
from decimal import Decimal

# Django
from django.contrib.auth.decorators import login_required, permission_required
from django.core.handlers.wsgi import WSGIRequest
from django.db.models import Count, Subquery, Sum
from django.db.models.functions import TruncDay
from django.http import Http404, HttpResponse, JsonResponse
from django.shortcuts import render

# Alliance Auth
from allianceauth.eveonline.models import EveCharacter

from .models import KillmailParticipant, MonthlyKillmail
from .utils import format_isk, get_current_month_range, get_month_range, safe_int

# API pagination limits
MAX_PAGE_LENGTH = 100
MAX_RECENT_KILLS_LIMIT = 100
MAX_TOP_KILLS_LIMIT = 50


def get_month_params_from_request(request):
    """
    Extract year/month from request GET params.
    Returns (start, end, year, month, is_current).
    """
    now = datetime.now(dt_timezone.utc)
    year = request.GET.get("year")
    month = request.GET.get("month")

    if year and month:
        try:
            year = int(year)
            month = int(month)
            if month < 1 or month > 12:
                raise ValueError("Invalid month")
            start, end = get_month_range(year, month)
            is_current = year == now.year and month == now.month
            return start, end, year, month, is_current
        except (ValueError, TypeError):
            pass

    # Default to current month
    start, end = get_current_month_range()
    return start, end, now.year, now.month, True


@login_required
@permission_required("aatps.basic_access")
def dashboard(request: WSGIRequest) -> HttpResponse:
    """Main dashboard showing current month activity."""
    month_start, month_end = get_current_month_range()
    now = datetime.now(dt_timezone.utc)

    # Get current user's characters
    user_chars = EveCharacter.objects.filter(character_ownership__user=request.user)

    context = {
        "month_name": month_start.strftime("%B %Y"),
        "year": now.year,
        "month": now.month,
        "month_start": month_start,
        "month_end": month_end,
        "user_characters": user_chars,
        "is_current_month": True,
    }
    return render(request, "aatps/dashboard.html", context)


@login_required
@permission_required("aatps.basic_access")
def historical_view(request: WSGIRequest, year: int, month: int) -> HttpResponse:
    """View historical data for a specific month."""
    now = datetime.now(dt_timezone.utc)

    # Validate month
    if month < 1 or month > 12:
        raise Http404("Invalid month")

    try:
        target_date = datetime(year, month, 1, tzinfo=dt_timezone.utc)
    except ValueError:
        raise Http404("Invalid date")

    # Don't allow future months
    if target_date > now:
        raise Http404("Cannot view future data")

    # Calculate date range for the requested month
    month_start, month_end = get_month_range(year, month)

    # Get current user's characters
    user_chars = EveCharacter.objects.filter(character_ownership__user=request.user)

    context = {
        "month_name": target_date.strftime("%B %Y"),
        "year": year,
        "month": month,
        "month_start": month_start,
        "month_end": month_end,
        "user_characters": user_chars,
        "is_current_month": (year == now.year and month == now.month),
    }
    return render(request, "aatps/dashboard.html", context)


@login_required
@permission_required("aatps.basic_access")
def stats_api(request: WSGIRequest) -> JsonResponse:
    """Return overall statistics for the dashboard."""
    month_start, month_end, year, month, is_current = get_month_params_from_request(request)

    # Get all killmails for the month
    killmails = MonthlyKillmail.objects.filter(
        killmail_time__gte=month_start,
        killmail_time__lte=month_end,
    )

    # Get participants to determine kills vs losses
    participants = KillmailParticipant.objects.filter(
        killmail__killmail_time__gte=month_start,
        killmail__killmail_time__lte=month_end,
    )

    # Use subqueries instead of materializing sets in memory
    # Kills: killmails where we have non-victim participants
    kill_km_subquery = participants.filter(is_victim=False).values("killmail_id").distinct()

    # Losses: killmails where we have victim participants
    loss_km_subquery = participants.filter(is_victim=True).values("killmail_id").distinct()

    # Get aggregates for kills
    kills_qs = killmails.filter(killmail_id__in=Subquery(kill_km_subquery))
    kills_stats = kills_qs.aggregate(total_kills=Count("killmail_id"), total_kill_value=Sum("total_value"))

    # Get aggregates for losses
    losses_qs = killmails.filter(killmail_id__in=Subquery(loss_km_subquery))
    losses_stats = losses_qs.aggregate(total_losses=Count("killmail_id"), total_loss_value=Sum("total_value"))

    total_kills = kills_stats["total_kills"] or 0
    total_kill_value = kills_stats["total_kill_value"] or 0
    total_losses = losses_stats["total_losses"] or 0
    total_loss_value = losses_stats["total_loss_value"] or 0

    # Calculate efficiency
    if total_kill_value and total_loss_value:
        efficiency = float(total_kill_value) / (float(total_kill_value) + float(total_loss_value)) * 100
    elif total_kill_value:
        efficiency = 100.0
    else:
        efficiency = 0.0

    # Count active pilots (unique users with participation)
    active_pilots = participants.filter(user__isnull=False).values("user").distinct().count()

    return JsonResponse(
        {
            "total_kills": total_kills,
            "total_losses": total_losses,
            "total_kill_value": float(total_kill_value),
            "total_loss_value": float(total_loss_value),
            "total_kill_value_formatted": format_isk(total_kill_value),
            "total_loss_value_formatted": format_isk(total_loss_value),
            "efficiency": round(efficiency, 1),
            "active_pilots": active_pilots,
            "year": year,
            "month": month,
        }
    )


@login_required
@permission_required("aatps.basic_access")
def activity_api(request: WSGIRequest) -> JsonResponse:
    """Returns daily activity data for charts."""
    month_start, month_end, year, month, is_current = get_month_params_from_request(request)

    # Get participants for the month
    participants = KillmailParticipant.objects.filter(
        killmail__killmail_time__gte=month_start,
        killmail__killmail_time__lte=month_end,
    )

    # Use subqueries instead of materializing sets in memory
    kill_km_subquery = participants.filter(is_victim=False).values("killmail_id").distinct()

    loss_km_subquery = participants.filter(is_victim=True).values("killmail_id").distinct()

    # Get daily kills
    daily_kills = (
        MonthlyKillmail.objects.filter(
            killmail_time__gte=month_start, killmail_time__lte=month_end, killmail_id__in=Subquery(kill_km_subquery)
        )
        .annotate(day=TruncDay("killmail_time"))
        .values("day")
        .annotate(kills=Count("killmail_id"), kill_value=Sum("total_value"))
        .order_by("day")
    )

    # Get daily losses
    daily_losses = (
        MonthlyKillmail.objects.filter(
            killmail_time__gte=month_start, killmail_time__lte=month_end, killmail_id__in=Subquery(loss_km_subquery)
        )
        .annotate(day=TruncDay("killmail_time"))
        .values("day")
        .annotate(losses=Count("killmail_id"), loss_value=Sum("total_value"))
        .order_by("day")
    )

    # Combine into a single structure keyed by day
    data = {}
    for row in daily_kills:
        day_str = row["day"].strftime("%Y-%m-%d")
        data[day_str] = {
            "day": day_str,
            "kills": row["kills"],
            "losses": 0,
            "kill_value": float(row["kill_value"] or 0),
            "loss_value": 0,
        }

    for row in daily_losses:
        day_str = row["day"].strftime("%Y-%m-%d")
        if day_str in data:
            data[day_str]["losses"] = row["losses"]
            data[day_str]["loss_value"] = float(row["loss_value"] or 0)
        else:
            data[day_str] = {
                "day": day_str,
                "kills": 0,
                "losses": row["losses"],
                "kill_value": 0,
                "loss_value": float(row["loss_value"] or 0),
            }

    # Sort by day and return as list
    sorted_data = sorted(data.values(), key=lambda x: x["day"])

    return JsonResponse({"data": sorted_data})


@login_required
@permission_required("aatps.basic_access")
def leaderboard_api(request: WSGIRequest) -> JsonResponse:
    """Server-side DataTables API for leaderboard."""
    month_start, month_end, year, month, is_current = get_month_params_from_request(request)

    # DataTables parameters
    draw = safe_int(request.GET.get("draw"), default=1, min_val=1)
    start = safe_int(request.GET.get("start"), default=0, min_val=0)
    length = safe_int(request.GET.get("length"), default=10, min_val=1, max_val=MAX_PAGE_LENGTH)
    search_value = request.GET.get("search[value]", "").lower()

    # Get all non-victim participations for the month
    participations = KillmailParticipant.objects.filter(
        killmail__killmail_time__gte=month_start,
        killmail__killmail_time__lte=month_end,
        is_victim=False,
    ).select_related("killmail", "user", "character")

    # Group by user (or character if no user)
    # We need to aggregate kills and values, deduplicating when multiple
    # characters from the same user are on the same killmail
    groups = {}

    for p in participations:
        km_id = p.killmail_id
        val = float(p.killmail.total_value)

        if p.user:
            key = ("U", p.user.id)
            # Get main character name
            try:
                main_char = p.user.profile.main_character
                display_name = main_char.character_name if main_char else p.character.character_name
                portrait_id = main_char.character_id if main_char else p.character.character_id
            except Exception:
                display_name = p.character.character_name
                portrait_id = p.character.character_id
        else:
            key = ("C", p.character.character_id)
            display_name = p.character.character_name
            portrait_id = p.character.character_id

        if key not in groups:
            groups[key] = {
                "character_name": display_name,
                "portrait_id": portrait_id,
                "kills_set": set(),
                "kill_value": 0.0,
                "final_blows": 0,
            }

        # Only count each killmail once per user
        if km_id not in groups[key]["kills_set"]:
            groups[key]["kills_set"].add(km_id)
            groups[key]["kill_value"] += val

        if p.is_final_blow:
            groups[key]["final_blows"] += 1

    # Convert to list
    data_list = []
    for key, stats in groups.items():
        stats["group_key"] = key
        stats["kills"] = len(stats.pop("kills_set"))
        data_list.append(stats)

    # Calculate total records before filtering
    records_total = len(data_list)

    # Filtering (search)
    if search_value:
        data_list = [d for d in data_list if search_value in d["character_name"].lower()]

    records_filtered = len(data_list)

    # Sorting
    order_column_index = request.GET.get("order[0][column]")
    order_dir = request.GET.get("order[0][dir]", "desc")

    sort_columns = {
        "0": "character_name",
        "1": "kills",
        "2": "final_blows",
        "3": "kill_value",
    }
    sort_field = sort_columns.get(order_column_index, "kill_value")
    data_list.sort(key=lambda x: x.get(sort_field, 0), reverse=(order_dir == "desc"))

    # Paging
    paged_data = data_list[start : start + length]

    # Add rank and format values
    for i, entry in enumerate(paged_data):
        entry.pop("group_key", None)
        global_index = start + i
        entry["rank"] = global_index + 1
        entry["kill_value_formatted"] = format_isk(entry["kill_value"])

    return JsonResponse(
        {
            "draw": draw,
            "recordsTotal": records_total,
            "recordsFiltered": records_filtered,
            "data": paged_data,
        }
    )


@login_required
@permission_required("aatps.basic_access")
def top_kills_api(request: WSGIRequest) -> JsonResponse:
    """Returns top kills for the month."""
    month_start, month_end, year, month, is_current = get_month_params_from_request(request)
    limit = safe_int(request.GET.get("limit"), default=10, min_val=1, max_val=MAX_TOP_KILLS_LIMIT)

    # Get kill killmail IDs (killmails with non-victim participants)
    kill_km_ids = (
        KillmailParticipant.objects.filter(
            killmail__killmail_time__gte=month_start,
            killmail__killmail_time__lte=month_end,
            is_victim=False,
        )
        .values_list("killmail_id", flat=True)
        .distinct()
    )

    # Get top kills by value
    top_kills = MonthlyKillmail.objects.filter(
        killmail_time__gte=month_start,
        killmail_time__lte=month_end,
        killmail_id__in=kill_km_ids,
    ).order_by("-total_value")[:limit]

    data = []
    for km in top_kills:
        data.append(
            {
                "killmail_id": km.killmail_id,
                "ship_type_id": km.ship_type_id,
                "ship_type_name": km.ship_type_name,
                "victim_name": km.victim_name,
                "victim_corp_name": km.victim_corp_name,
                "total_value": float(km.total_value),
                "total_value_formatted": format_isk(km.total_value),
                "killmail_time": km.killmail_time.isoformat(),
                "solar_system_name": km.solar_system_name,
            }
        )

    return JsonResponse({"data": data})


@login_required
@permission_required("aatps.basic_access")
def ship_stats_api(request: WSGIRequest) -> JsonResponse:
    """Returns ship class statistics for the month."""
    month_start, month_end, year, month, is_current = get_month_params_from_request(request)

    # Get participants for the month
    participants = KillmailParticipant.objects.filter(
        killmail__killmail_time__gte=month_start,
        killmail__killmail_time__lte=month_end,
    )

    # Use subqueries instead of materializing sets in memory
    kill_km_subquery = participants.filter(is_victim=False).values("killmail_id").distinct()

    loss_km_subquery = participants.filter(is_victim=True).values("killmail_id").distinct()

    # Get ship group stats for kills
    kill_stats = (
        MonthlyKillmail.objects.filter(
            killmail_time__gte=month_start, killmail_time__lte=month_end, killmail_id__in=Subquery(kill_km_subquery)
        )
        .values("ship_group_name")
        .annotate(count=Count("killmail_id"))
        .order_by("-count")
    )

    # Get ship group stats for losses
    loss_stats = (
        MonthlyKillmail.objects.filter(
            killmail_time__gte=month_start, killmail_time__lte=month_end, killmail_id__in=Subquery(loss_km_subquery)
        )
        .values("ship_group_name")
        .annotate(count=Count("killmail_id"))
        .order_by("-count")
    )

    # Combine into a single structure
    ship_stats = {}
    for row in kill_stats:
        group = row["ship_group_name"] or "Unknown"
        if group not in ship_stats:
            ship_stats[group] = {"killed": 0, "lost": 0}
        ship_stats[group]["killed"] += row["count"]

    for row in loss_stats:
        group = row["ship_group_name"] or "Unknown"
        if group not in ship_stats:
            ship_stats[group] = {"killed": 0, "lost": 0}
        ship_stats[group]["lost"] += row["count"]

    # Convert to list format for charts
    data = [
        {
            "ship_group": group,
            "killed": stats["killed"],
            "lost": stats["lost"],
        }
        for group, stats in sorted(ship_stats.items(), key=lambda x: x[1]["killed"], reverse=True)
    ]

    return JsonResponse({"data": data})


@login_required
@permission_required("aatps.basic_access")
def my_stats_api(request: WSGIRequest) -> JsonResponse:
    """Return personal statistics for the current user."""
    month_start, month_end, year, month, is_current = get_month_params_from_request(request)

    # Get user's character IDs
    user_char_ids = list(
        EveCharacter.objects.filter(character_ownership__user=request.user).values_list("character_id", flat=True)
    )

    if not user_char_ids:
        return JsonResponse(
            {
                "kills": 0,
                "losses": 0,
                "kill_value": 0,
                "loss_value": 0,
                "kill_value_formatted": "0",
                "loss_value_formatted": "0",
                "final_blows": 0,
                "efficiency": 0,
                "rank": None,
                "favorite_ship": None,
            }
        )

    # Get user's participations
    participations = KillmailParticipant.objects.filter(
        character__character_id__in=user_char_ids,
        killmail__killmail_time__gte=month_start,
        killmail__killmail_time__lte=month_end,
    ).select_related("killmail")

    # Calculate kills (unique killmails where user is attacker)
    kill_kms = set()
    kill_value = Decimal("0")
    final_blows = 0
    ship_counts = {}

    for p in participations.filter(is_victim=False):
        if p.killmail_id not in kill_kms:
            kill_kms.add(p.killmail_id)
            kill_value += p.killmail.total_value
        if p.is_final_blow:
            final_blows += 1
        # Track ships used
        if p.ship_type_name and p.ship_type_name != "Unknown":
            ship_counts[p.ship_type_name] = ship_counts.get(p.ship_type_name, 0) + 1

    # Calculate losses (unique killmails where user is victim)
    loss_kms = set()
    loss_value = Decimal("0")

    for p in participations.filter(is_victim=True):
        if p.killmail_id not in loss_kms:
            loss_kms.add(p.killmail_id)
            loss_value += p.killmail.total_value

    kills = len(kill_kms)
    losses = len(loss_kms)

    # Calculate efficiency
    if kill_value and loss_value:
        efficiency = float(kill_value) / (float(kill_value) + float(loss_value)) * 100
    elif kill_value:
        efficiency = 100.0
    else:
        efficiency = 0.0

    # Get favorite ship
    favorite_ship = None
    if ship_counts:
        favorite_ship = max(ship_counts.items(), key=lambda x: x[1])[0]

    # Calculate rank (compare kill value to all users)
    rank = None
    if kills > 0:
        # Get all users' kill values for the month
        all_participations = KillmailParticipant.objects.filter(
            killmail__killmail_time__gte=month_start,
            killmail__killmail_time__lte=month_end,
            is_victim=False,
            user__isnull=False,
        ).select_related("killmail")

        user_values = {}
        for p in all_participations:
            uid = p.user_id
            if uid not in user_values:
                user_values[uid] = {"kms": set(), "value": Decimal("0")}
            if p.killmail_id not in user_values[uid]["kms"]:
                user_values[uid]["kms"].add(p.killmail_id)
                user_values[uid]["value"] += p.killmail.total_value

        # Sort by value descending
        sorted_users = sorted(user_values.items(), key=lambda x: x[1]["value"], reverse=True)
        for i, (uid, data) in enumerate(sorted_users, 1):
            if uid == request.user.id:
                rank = i
                break

    return JsonResponse(
        {
            "kills": kills,
            "losses": losses,
            "kill_value": float(kill_value),
            "loss_value": float(loss_value),
            "kill_value_formatted": format_isk(kill_value),
            "loss_value_formatted": format_isk(loss_value),
            "final_blows": final_blows,
            "efficiency": round(efficiency, 1),
            "rank": rank,
            "favorite_ship": favorite_ship,
        }
    )


@login_required
@permission_required("aatps.basic_access")
def recent_kills_api(request: WSGIRequest) -> JsonResponse:
    """Returns recent killmails for the month."""
    month_start, month_end, year, month, is_current = get_month_params_from_request(request)
    limit = safe_int(request.GET.get("limit"), default=50, min_val=1, max_val=MAX_RECENT_KILLS_LIMIT)
    user_only = request.GET.get("user_only", "false").lower() == "true"

    # Base query for killmails with participants
    participants_qs = KillmailParticipant.objects.filter(
        killmail__killmail_time__gte=month_start,
        killmail__killmail_time__lte=month_end,
    )

    if user_only:
        # Get user's character IDs
        user_char_ids = list(
            EveCharacter.objects.filter(character_ownership__user=request.user).values_list("character_id", flat=True)
        )
        participants_qs = participants_qs.filter(character__character_id__in=user_char_ids)

    # Get killmail IDs and whether they are kills or losses for this user/group
    km_data = {}
    for p in participants_qs.select_related("killmail"):
        km_id = p.killmail_id
        if km_id not in km_data:
            km_data[km_id] = {
                "killmail": p.killmail,
                "is_loss": p.is_victim,
            }
        # If any participant is a victim, it's a loss
        if p.is_victim:
            km_data[km_id]["is_loss"] = True

    # Sort by time descending and limit
    sorted_kms = sorted(km_data.values(), key=lambda x: x["killmail"].killmail_time, reverse=True)[:limit]

    data = []
    for item in sorted_kms:
        km = item["killmail"]
        data.append(
            {
                "killmail_id": km.killmail_id,
                "killmail_time": km.killmail_time.isoformat(),
                "ship_type_id": km.ship_type_id,
                "ship_type_name": km.ship_type_name,
                "victim_name": km.victim_name,
                "victim_corp_name": km.victim_corp_name,
                "total_value": float(km.total_value),
                "total_value_formatted": format_isk(km.total_value),
                "solar_system_name": km.solar_system_name,
                "is_loss": item["is_loss"],
                "final_blow_char_name": km.final_blow_char_name,
            }
        )

    return JsonResponse({"data": data})
