from collections import defaultdict
from django.http import HttpRequest
from ninja import Router
from ninja.errors import HttpError
from django.core.cache import cache
from django.shortcuts import get_object_or_404

from accounts.models import CustomUser
from animations.schemas import (
    RankingSchema,
    TournamentSubmissionsOut,
    TournamentPuzzleResultsOut,
    PuzzleSubmissionsOut,
    PuzzleResultsOut,
    PuzzleSubmissionWithRankOut,
    PuzzlePointsFactorOut,
    WinnerResponseOut,
    WinnerFileOut,
)
from submissions.models import PuzzleResponse, SteamCollectionItem, SteamCollection


router = Router()


@router.get("results", response=RankingSchema)
def results(request: HttpRequest) -> dict:
    cache_key = "api:results:results"
    cached_data = cache.get(cache_key)

    if cached_data is not None:
        return cached_data

    responses_by_userid = defaultdict(list)
    responses_by_puzzleid = defaultdict(list)

    for response in list(
        PuzzleResponse.objects.filter(needs_manual_validation=False)
        .filter_user_best_response()
        .prefetch_related("submission__user")
    ):
        responses_by_userid[response.submission.user.id].append(response)
        responses_by_puzzleid[response.puzzle.id].append(response)

    ranking = {}

    for puzzle_id, responses in responses_by_puzzleid.items():
        ranking[puzzle_id] = sorted(
            responses, key=lambda x: (x.rank_points is None, x.rank_points or 0)
        )

    data = {
        "users": list(CustomUser.objects.filter(pk__in=responses_by_userid.keys())),
        "puzzles": list(SteamCollectionItem.objects.all()),
        "responses_by_userid": dict(responses_by_userid),
        "ranking_by_puzzle": ranking,
    }

    cache.set("api:results:results", data, 300)
    return data


@router.get("top-submissions", response=TournamentSubmissionsOut)
def top_submissions(request: HttpRequest, limit: int = 5) -> dict:
    """Get tournament top submissions for each puzzle. Only available when tournament is closed."""
    cache_key = f"api:results:top_submissions:{limit}"
    cached_data = cache.get(cache_key)

    if cached_data is not None:
        return cached_data

    collection = get_object_or_404(SteamCollection, is_active=True)

    # Only allow access when tournament is closed
    if collection.accepting_submissions:
        raise HttpError(403, "Tournament is still accepting submissions")

    # Get all puzzles
    puzzles = SteamCollectionItem.objects.filter(collection=collection).order_by(
        "order_index"
    )

    # Build response
    submissions_list = []
    for puzzle in puzzles:
        # Get the top N responses for this puzzle (ranked by points, highest first)
        top_responses = (
            PuzzleResponse.objects.filter(puzzle=puzzle, needs_manual_validation=False)
            .filter_user_best_response()
            .annotate_rank_points()
            .order_by("-rank_points")[:limit]
        )

        # Build submission list for this puzzle
        puzzle_submissions = []
        for response in top_responses:
            # Get submission files
            files = response.files.all()
            response_files = [
                WinnerFileOut(
                    file_url=file.file_url or "",
                    original_filename=file.original_filename,
                )
                for file in files
            ]

            # Calculate total coefficient
            total_coef = None
            if (
                puzzle.points_factor
                and response.final_cost is not None
                and response.final_cycles is not None
                and response.final_area is not None
            ):
                total_coef = (
                    puzzle.points_factor.cost * response.final_cost
                    + puzzle.points_factor.cycles * response.final_cycles
                    + puzzle.points_factor.area * response.final_area
                )

            submission_data = WinnerResponseOut(
                user_id=response.submission.user.id if response.submission.user else 0,
                username=response.submission.user.username
                if response.submission.user
                else "Anonymous",
                final_cost=response.final_cost,
                final_cycles=response.final_cycles,
                final_area=response.final_area,
                rank_points=response.rank_points,
                total_coef=total_coef,
                files=response_files,
            )
            puzzle_submissions.append(submission_data)

        submissions_list.append(
            PuzzleSubmissionsOut(
                puzzle_id=puzzle.id,
                puzzle_title=puzzle.title,
                submissions=puzzle_submissions,
            )
        )

    data = {"submissions": submissions_list}
    cache.set(f"api:results:top_submissions:{limit}", data, 300)
    return data


@router.get("puzzle-results", response=TournamentPuzzleResultsOut)
def puzzle_results(request: HttpRequest, limit: int = 5) -> dict:
    """Get tournament results organized by puzzle with coefficients. Only available when tournament is closed."""
    cache_key = f"api:results:puzzle_results:{limit}"
    cached_data = cache.get(cache_key)

    if cached_data is not None:
        return cached_data

    collection = get_object_or_404(SteamCollection, is_active=True)

    # Only allow access when tournament is closed
    if collection.accepting_submissions:
        raise HttpError(403, "Tournament is still accepting submissions")

    # Get all puzzles
    puzzles = SteamCollectionItem.objects.filter(collection=collection).order_by(
        "order_index"
    )

    # Build response
    results_list = []
    for puzzle in puzzles:
        # Get the top N responses for this puzzle (ranked by points)
        top_responses = (
            PuzzleResponse.objects.filter(puzzle=puzzle, needs_manual_validation=False)
            .filter_user_best_response()
            .annotate_rank_points()
            .order_by("-rank_points")[:limit]
        )

        # Build submission list for this puzzle with rank
        puzzle_submissions = []
        for rank, response in enumerate(top_responses, 1):
            # Get submission files
            files = response.files.all()
            response_files = [
                WinnerFileOut(
                    file_url=file.file_url or "",
                    original_filename=file.original_filename,
                )
                for file in files
            ]

            # Calculate total coefficient
            total_coef = None
            if (
                puzzle.points_factor
                and response.final_cost is not None
                and response.final_cycles is not None
                and response.final_area is not None
            ):
                total_coef = (
                    puzzle.points_factor.cost * response.final_cost
                    + puzzle.points_factor.cycles * response.final_cycles
                    + puzzle.points_factor.area * response.final_area
                )

            submission_data = PuzzleSubmissionWithRankOut(
                rank=rank,
                user_id=response.submission.user.id if response.submission.user else 0,
                username=response.submission.user.username
                if response.submission.user
                else "Anonymous",
                final_cost=response.final_cost,
                final_cycles=response.final_cycles,
                final_area=response.final_area,
                rank_points=response.rank_points,
                total_coef=total_coef,
                files=response_files,
            )
            puzzle_submissions.append(submission_data)

        # Get points factor if available
        points_factor = None
        if puzzle.points_factor:
            points_factor = PuzzlePointsFactorOut(
                cost=puzzle.points_factor.cost,
                cycles=puzzle.points_factor.cycles,
                area=puzzle.points_factor.area,
            )

        results_list.append(
            PuzzleResultsOut(
                puzzle_id=puzzle.id,
                puzzle_title=puzzle.title,
                points_factor=points_factor,
                submissions=puzzle_submissions,
            )
        )

    data = {"results": results_list}
    cache.set(f"api:results:puzzle_results:{limit}", data, 300)
    return data
