from django.db import connection
from django.utils.translation import gettext as _, gettext_lazy

from judge.contest_format.ioi import IOIContestFormat
from judge.contest_format.registry import register_contest_format
from judge.timezone import from_database_time, to_database_time


@register_contest_format("ioi16")
class NewIOIContestFormat(IOIContestFormat):
    name = gettext_lazy("New IOI")
    config_defaults = {"cumtime": False}
    has_hidden_subtasks = True
    """
        cumtime: Specify True if time penalties are to be computed. Defaults to False.
    """

    def get_hidden_subtasks(self):
        queryset = self.contest.contest_problems.values_list("id", "hidden_subtasks")
        res = {}
        for problem_id, hidden_subtasks in queryset:
            subtasks = set()
            if hidden_subtasks:
                hidden_subtasks = hidden_subtasks.split(",")
                for i in hidden_subtasks:
                    try:
                        subtasks.add(int(i))
                    except Exception as e:
                        pass
            res[str(problem_id)] = subtasks
        return res

    def get_results_by_subtask(self, participation, include_frozen=False):
        frozen_time = self.contest.end_time
        if self.contest.freeze_after and not include_frozen:
            frozen_time = participation.start + self.contest.freeze_after

        with connection.cursor() as cursor:
            cursor.execute(
                """
                SELECT q.prob,
                       q.prob_points,
                       MIN(q.date) as `date`,
                       q.batch_points,
                       q.total_batch_points,
                       q.batch,
                       q.subid
                FROM (
                         SELECT cp.id          as `prob`,
                                cp.points      as `prob_points`,
                                sub.id         as `subid`,
                                sub.date       as `date`,
                                tc.points      as `points`,
                                tc.batch       as `batch`,
                                SUM(tc.points) as `batch_points`,
                                SUM(tc.total)  as `total_batch_points`
                         FROM judge_contestproblem cp
                                  INNER JOIN
                              judge_contestsubmission cs
                              ON (cs.problem_id = cp.id AND cs.participation_id = %s)
                                  LEFT OUTER JOIN
                              judge_submission sub
                              ON (sub.id = cs.submission_id AND sub.status = 'D')
                                  INNER JOIN judge_submissiontestcase tc
                              ON sub.id = tc.submission_id
                         WHERE sub.date < %s
                         GROUP BY cp.id, tc.batch, sub.id
                     ) q
                         INNER JOIN (
                    SELECT prob, batch, MAX(r.batch_points) as max_batch_points
                    FROM (
                             SELECT cp.id          as `prob`,
                                    tc.batch       as `batch`,
                                    SUM(tc.points) as `batch_points`
                             FROM judge_contestproblem cp
                                      INNER JOIN
                                  judge_contestsubmission cs
                                  ON (cs.problem_id = cp.id AND cs.participation_id = %s)
                                      LEFT OUTER JOIN
                                  judge_submission sub
                                  ON (sub.id = cs.submission_id AND sub.status = 'D')
                                      INNER JOIN judge_submissiontestcase tc
                                  ON sub.id = tc.submission_id
                             WHERE sub.date < %s
                             GROUP BY cp.id, tc.batch, sub.id
                         ) r
                    GROUP BY prob, batch
                ) p
                ON p.prob = q.prob AND (p.batch = q.batch OR p.batch is NULL AND q.batch is NULL)
                WHERE p.max_batch_points = q.batch_points
                GROUP BY q.prob, q.batch
            """,
                (
                    participation.id,
                    to_database_time(frozen_time),
                    participation.id,
                    to_database_time(frozen_time),
                ),
            )

            return cursor.fetchall()

    def update_participation(self, participation):
        hidden_subtasks = self.get_hidden_subtasks()

        def calculate_format_data(participation, include_frozen):
            format_data = {}
            for (
                problem_id,
                problem_points,
                time,
                subtask_points,
                total_subtask_points,
                subtask,
                sub_id,
            ) in self.get_results_by_subtask(participation, include_frozen):
                problem_id = str(problem_id)
                time = from_database_time(time)
                if self.config["cumtime"]:
                    dt = (time - participation.start).total_seconds()
                else:
                    dt = 0

                if format_data.get(problem_id) is None:
                    format_data[problem_id] = {
                        "points": 0,
                        "time": 0,
                        "total_points": 0,
                    }
                if (
                    subtask not in hidden_subtasks.get(problem_id, set())
                    or include_frozen
                ):
                    format_data[problem_id]["points"] += subtask_points
                format_data[problem_id]["total_points"] += total_subtask_points
                format_data[problem_id]["time"] = max(
                    dt, format_data[problem_id]["time"]
                )
                format_data[problem_id]["problem_points"] = problem_points

            return format_data

        def recalculate_results(format_data):
            cumtime = 0
            score = 0
            for problem_data in format_data.values():
                if not problem_data["total_points"]:
                    continue
                penalty = problem_data["time"]
                problem_data["points"] = (
                    problem_data["points"]
                    / problem_data["total_points"]
                    * problem_data["problem_points"]
                )
                if self.config["cumtime"] and problem_data["points"]:
                    cumtime += penalty
                score += problem_data["points"]
            return score, cumtime

        format_data = calculate_format_data(participation, False)
        score, cumtime = recalculate_results(format_data)
        self.handle_frozen_state(participation, format_data)
        participation.cumtime = max(cumtime, 0)
        participation.score = round(score, self.contest.points_precision)
        participation.tiebreaker = 0
        participation.format_data = format_data

        format_data_final = calculate_format_data(participation, True)
        score_final, cumtime_final = recalculate_results(format_data_final)
        participation.cumtime_final = max(cumtime_final, 0)
        participation.score_final = round(score_final, self.contest.points_precision)
        participation.format_data_final = format_data_final
        participation.save()
