from django.contrib.auth.models import User
from django.db import models
from django.utils import timezone
from typing_extensions import override


class CompletedLevel(models.Model):
    """Keeps track of which levels each user completed and when."""

    user = models.ForeignKey(User, on_delete=models.CASCADE)
    level = models.PositiveIntegerField()
    completed = models.BooleanField(default=False)
    hints_used = models.PositiveIntegerField(default=0)
    completion_date = models.DateTimeField(null=True, blank=True)
    client_ip = models.TextField(blank=True, default="")

    class Meta:  # pyright: ignore [ reportIncompatibleVariableOverride ]
        unique_together = ("user", "level")

    @override
    def __str__(self) -> str:
        return f"LevelCompletion(user={self.user.username}, level={self.level}, completed={self.completed})"

    @classmethod
    def check_completed(cls, user: User, level: int) -> bool:
        """Check if a specific level has already been completed by a user."""
        return cls.objects.filter(user=user, level=level, completed=True).exists()

    @classmethod
    def add_completion(cls, user: User, ip_address: str | None, level: int) -> bool:
        """
        Mark a level as completed for given user.

        Creates a new record if none exists, updates it if it exists but marked as incomplete,
        or leaves it unchanged if already completed.

        :return bool:
            ``True`` if a new record was created or an existing record was updated & marked completed.
            ``False`` if a completion record already existed and no changes were made.
        """
        # Use blank strings, not NULLs
        if not ip_address:
            ip_address = ""

        # Try to fetch an existing record for given user & level
        existing_record = cls.objects.filter(user=user, level=level).first()

        if not existing_record:
            # If no record exists, create a new one
            cls.objects.create(
                user=user,
                level=level,
                completed=True,
                completion_date=timezone.now(),
                client_ip=ip_address,
            )
            return True

        if not existing_record.completed:
            # If the record exists, but is marked as incomplete, update it
            existing_record.completed = True
            existing_record.completion_date = timezone.now()
            existing_record.client_ip = ip_address
            existing_record.save()
            return True

        # The record exists and is already marked completed
        return False

    @classmethod
    def check_hint(cls, user: User, level: int) -> int:
        """Check which highest hint number a user has requested hint for this level."""
        try:
            record = cls.objects.get(user=user, level=level)
            return record.hints_used
        except cls.DoesNotExist:
            return 0

    @classmethod
    def add_hint(cls, user: User, ip_address: str | None, level: int, hint_num: int) -> bool:
        """
        Record how many hints a user requested while solving the level.

        - If there is no completion record for this user & level, a new record will be created.
          This record will have ``completed=False`` and ``hints_used=hint_num``.
        - If the record already exists, and has ``completed=False``:
            - If this record already has a higher ``hints_used`` than the requested ``hint_num``,
              nothing will happen.
            - Otherwise, the record will be updated with the requested hint number.
        - If the record already exists, and has ``completed=True``, nothing will happen.
          Hints aren't marked retroactively, requesting a hint after you already solved this level is fine.

        :return bool:
            ``True`` if a new record was created or an existing record was updated & marked with hint_num.
            ``False`` if a completion record already existed and no changes were made.
        """
        # Use blank strings, not NULLs
        if not ip_address:
            ip_address = ""

        existing_record, created = cls.objects.get_or_create(
            user=user,
            level=level,
            defaults={"hints_used": hint_num, "client_ip": ip_address},
        )
        if created:
            # A new record was created with `used_hint=True`
            return True

        if not existing_record.completed and existing_record.hints_used < hint_num:
            # The user haven't yet completed this level, and the requested hint number
            # is higher than the logged amount of used hints
            existing_record.hints_used = hint_num
            existing_record.client_ip = ip_address
            existing_record.save()
            return True

        # Either the level is already completed, or the hint use was already recorded; no change
        return False
