from operator import attrgetter

from django.db.models import F, Prefetch
from django.http import Http404, JsonResponse
from django.shortcuts import get_object_or_404

from dmoj import settings
from judge.models import (
    Contest,
    ContestParticipation,
    ContestTag,
    Problem,
    Profile,
    Submission,
)


def sane_time_repr(delta):
    days = delta.days
    hours = delta.seconds / 3600
    minutes = (delta.seconds % 3600) / 60
    return "%02d:%02d:%02d" % (days, hours, minutes)


def api_v1_contest_list(request):
    queryset = Contest.get_visible_contests(request.user).prefetch_related(
        Prefetch("tags", queryset=ContestTag.objects.only("name"), to_attr="tag_list")
    )

    return JsonResponse(
        {
            c.key: {
                "name": c.name,
                "start_time": c.start_time.isoformat(),
                "end_time": c.end_time.isoformat(),
                "time_limit": c.time_limit and sane_time_repr(c.time_limit),
                "labels": list(map(attrgetter("name"), c.tag_list)),
            }
            for c in queryset
        }
    )


def api_v1_contest_detail(request, contest):
    contest = get_object_or_404(Contest, key=contest)

    in_contest = contest.is_in_contest(request.user)
    can_see_rankings = contest.can_see_full_scoreboard(request.user)
    problems = list(
        contest.contest_problems.select_related("problem")
        .defer("problem__description")
        .order_by("order")
    )
    participations = (
        contest.users.filter(virtual=0)
        .prefetch_related("user__organizations")
        .annotate(username=F("user__user__username"))
        .order_by("-score", "cumtime")
        if can_see_rankings
        else []
    )

    can_see_problems = (
        in_contest or contest.ended or contest.is_editable_by(request.user)
    )

    return JsonResponse(
        {
            "time_limit": contest.time_limit and contest.time_limit.total_seconds(),
            "start_time": contest.start_time.isoformat(),
            "end_time": contest.end_time.isoformat(),
            "tags": list(contest.tags.values_list("name", flat=True)),
            "is_rated": contest.is_rated,
            "rate_all": contest.is_rated and contest.rate_all,
            "has_rating": contest.ratings.exists(),
            "rating_floor": contest.rating_floor,
            "rating_ceiling": contest.rating_ceiling,
            "format": {
                "name": contest.format_name,
                "config": contest.format_config,
            },
            "problems": [
                {
                    "points": int(problem.points),
                    "partial": problem.partial,
                    "name": problem.problem.name,
                    "code": problem.problem.code,
                }
                for problem in problems
            ]
            if can_see_problems
            else [],
            "rankings": [
                {
                    "user": participation.username,
                    "points": participation.score,
                    "cumtime": participation.cumtime,
                    "is_disqualified": participation.is_disqualified,
                    "solutions": contest.format.get_problem_breakdown(
                        participation, problems
                    ),
                }
                for participation in participations
            ],
        }
    )


def api_v1_problem_list(request):
    queryset = Problem.objects.filter(is_public=True, is_organization_private=False)
    if settings.ENABLE_FTS and "search" in request.GET:
        query = " ".join(request.GET.getlist("search")).strip()
        if query:
            queryset = queryset.search(query)
    queryset = queryset.values_list(
        "code", "points", "partial", "name", "group__full_name"
    )

    return JsonResponse(
        {
            code: {
                "points": points,
                "partial": partial,
                "name": name,
                "group": group,
            }
            for code, points, partial, name, group in queryset
        }
    )


def api_v1_problem_info(request, problem):
    p = get_object_or_404(Problem, code=problem)
    if not p.is_accessible_by(request.user):
        raise Http404()

    return JsonResponse(
        {
            "name": p.name,
            "authors": list(p.authors.values_list("user__username", flat=True)),
            "types": list(p.types.values_list("full_name", flat=True)),
            "group": p.group.full_name,
            "time_limit": p.time_limit,
            "memory_limit": p.memory_limit,
            "points": p.points,
            "partial": p.partial,
            "languages": list(p.allowed_languages.values_list("key", flat=True)),
        }
    )


def api_v1_user_list(request):
    queryset = Profile.objects.filter(is_unlisted=False).values_list(
        "user__username", "points", "performance_points", "display_rank"
    )
    return JsonResponse(
        {
            username: {
                "points": points,
                "performance_points": performance_points,
                "rank": rank,
            }
            for username, points, performance_points, rank in queryset
        }
    )


def api_v1_user_info(request, user):
    profile = get_object_or_404(Profile, user__username=user)
    submissions = list(
        Submission.objects.filter(
            case_points=F("case_total"),
            user=profile,
            problem__is_public=True,
            problem__is_organization_private=False,
        )
        .values("problem")
        .distinct()
        .values_list("problem__code", flat=True)
    )
    resp = {
        "points": profile.points,
        "performance_points": profile.performance_points,
        "rank": profile.display_rank,
        "solved_problems": submissions,
        "organizations": list(profile.organizations.values_list("id", flat=True)),
    }

    last_rating = profile.ratings.last()

    contest_history = {}
    participations = ContestParticipation.objects.filter(
        user=profile,
        virtual=0,
        contest__is_visible=True,
        contest__is_private=False,
        contest__is_organization_private=False,
    )
    for contest_key, rating, mean, performance in participations.values_list(
        "contest__key",
        "rating__rating",
        "rating__mean",
        "rating__performance",
    ):
        contest_history[contest_key] = {
            "rating": rating,
            "raw_rating": mean,
            "performance": performance,
        }

    resp["contests"] = {
        "current_rating": last_rating.rating if last_rating else None,
        "history": contest_history,
    }

    return JsonResponse(resp)


def api_v1_user_submissions(request, user):
    profile = get_object_or_404(Profile, user__username=user)
    subs = Submission.objects.filter(
        user=profile, problem__is_public=True, problem__is_organization_private=False
    )

    return JsonResponse(
        {
            sub["id"]: {
                "problem": sub["problem__code"],
                "time": sub["time"],
                "memory": sub["memory"],
                "points": sub["points"],
                "language": sub["language__key"],
                "status": sub["status"],
                "result": sub["result"],
            }
            for sub in subs.values(
                "id",
                "problem__code",
                "time",
                "memory",
                "points",
                "language__key",
                "status",
                "result",
            )
        }
    )
