from typing import Self
from django.db import models
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
import uuid

from django.db.models.expressions import Window
from django.db.models.functions import Cast, Rank, RowNumber
from django.db.models.query import F

User = get_user_model()


class JsonIndex(models.Func):
    function = ""
    template = "%(json_field)s -> (%(index)s::int)"

    def __init__(self, json_field, index_expression, **extra):
        super().__init__(json_field, index_expression, **extra)
        self.output_field = models.IntegerField()

    def resolve_expression(
        self, query, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        # Resolve both expressions in the query context to ensure joins are set up correctly
        clone = self.copy()
        clone.source_expressions = [
            expr.resolve_expression(query, allow_joins, reuse, summarize, for_save)
            for expr in self.source_expressions
        ]
        return clone

    def as_sql(self, compiler, connection):
        json_sql, json_params = compiler.compile(self.source_expressions[0])
        idx_sql, idx_params = compiler.compile(self.source_expressions[1])
        sql = self.template % {"json_field": json_sql, "index": idx_sql}
        params = json_params + idx_params
        return sql, params


class SteamAPIKey(models.Model):
    """Model to store Steam API key configuration - Admin only"""

    name = models.CharField(
        max_length=100,
        unique=True,
        help_text="Descriptive name for this API key (e.g., 'Production Key', 'Development Key')",
    )
    api_key = models.CharField(
        max_length=64,
        help_text="Steam Web API key from https://steamcommunity.com/dev/apikey",
    )
    is_active = models.BooleanField(
        default=True, help_text="Whether this API key should be used"
    )
    description = models.TextField(
        blank=True, help_text="Optional description or notes about this API key"
    )

    # Metadata
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    last_used = models.DateTimeField(
        null=True, blank=True, help_text="When this API key was last used"
    )

    class Meta:
        verbose_name = "Steam API Key"
        verbose_name_plural = "Steam API Keys"
        ordering = ["-is_active", "name"]

    def __str__(self):
        status = "Active" if self.is_active else "Inactive"
        return f"{self.name} ({status})"

    def clean(self):
        """Validate the API key format"""
        if self.api_key:
            # Steam API keys are typically 32 characters of hexadecimal
            if len(self.api_key) != 32:
                raise ValidationError("Steam API key should be 32 characters long")

            # Check if it's hexadecimal
            try:
                int(self.api_key, 16)
            except ValueError:
                raise ValidationError(
                    "Steam API key should contain only hexadecimal characters (0-9, A-F)"
                )

    def save(self, *args, **kwargs):
        self.full_clean()
        super().save(*args, **kwargs)

    @classmethod
    def get_active_key(cls):
        """Get the currently active API key"""
        return cls.objects.filter(is_active=True).first()

    @property
    def masked_key(self):
        """Return a masked version of the API key for display"""
        if not self.api_key:
            return ""
        return f"{self.api_key[:8]}{'*' * 16}{self.api_key[-8:]}"


class SteamCollection(models.Model):
    """Model representing a Steam Workshop collection"""

    # Basic collection info
    steam_id = models.CharField(
        max_length=50, unique=True, help_text="Steam collection ID from URL"
    )
    url = models.URLField(help_text="Full Steam Workshop collection URL")
    title = models.CharField(max_length=255, blank=True, help_text="Collection title")
    description = models.TextField(blank=True, help_text="Collection description")

    # Author information
    author_name = models.CharField(
        max_length=100, blank=True, help_text="Steam username of collection creator"
    )
    author_steam_id = models.CharField(
        max_length=50, blank=True, help_text="Steam ID of collection creator"
    )

    # Collection metadata
    total_items = models.PositiveIntegerField(
        default=0, help_text="Number of items in collection"
    )
    unique_visitors = models.PositiveIntegerField(
        default=0, help_text="Number of unique visitors"
    )
    current_favorites = models.PositiveIntegerField(
        default=0, help_text="Current number of favorites"
    )
    total_favorites = models.PositiveIntegerField(
        default=0, help_text="Total unique favorites"
    )

    # Timestamps
    steam_created_date = models.DateTimeField(
        null=True, blank=True, help_text="When collection was created on Steam"
    )
    steam_updated_date = models.DateTimeField(
        null=True, blank=True, help_text="When collection was last updated on Steam"
    )

    # Local tracking
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    last_fetched = models.DateTimeField(
        null=True, blank=True, help_text="When data was last fetched from Steam"
    )

    # Status
    is_active = models.BooleanField(
        default=True, help_text="Whether this collection is actively tracked"
    )
    accepting_submissions = models.BooleanField(
        default=True, help_text="Whether the tournament is accepting new submissions"
    )
    fetch_error = models.TextField(
        blank=True, help_text="Last error encountered when fetching data"
    )

    class Meta:
        ordering = ["-created_at"]
        verbose_name = "Steam Collection"
        verbose_name_plural = "Steam Collections"

    def __str__(self):
        return f"{self.title or f'Collection {self.steam_id}'}"

    @property
    def steam_url(self):
        """Generate the Steam Workshop URL from steam_id"""
        return f"https://steamcommunity.com/workshop/filedetails/?id={self.steam_id}"


class SteamCollectionItem(models.Model):
    """Model representing individual items within a Steam collection"""

    # Relationships
    collection = models.ForeignKey(
        SteamCollection, on_delete=models.CASCADE, related_name="items"
    )

    # Item identification
    steam_item_id = models.CharField(max_length=50, help_text="Steam Workshop item ID")
    title = models.CharField(max_length=255, blank=True, help_text="Item title")

    # Author information
    author_name = models.CharField(
        max_length=100, blank=True, help_text="Steam username of item creator"
    )
    author_steam_id = models.CharField(
        max_length=50, blank=True, help_text="Steam ID of item creator"
    )

    # Item metadata
    description = models.TextField(blank=True, help_text="Item description")
    tags = models.JSONField(
        default=list, blank=True, help_text="Item tags as JSON array"
    )

    # Position in collection
    order_index = models.PositiveIntegerField(
        default=0, help_text="Order of item in collection"
    )

    # Timestamps
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    # Puzzle points
    points_factor = models.ForeignKey(
        "animations.PuzzlePointsFactor",
        null=True,
        on_delete=models.SET_NULL,
    )

    points_value = models.ForeignKey(
        "animations.PuzzlePointsValue",
        null=True,
        on_delete=models.SET_NULL,
    )

    class Meta:
        ordering = ["collection", "order_index"]
        unique_together = ["collection", "steam_item_id"]
        verbose_name = "Steam Collection Item"
        verbose_name_plural = "Steam Collection Items"

    def __str__(self):
        return f"{self.title or f'Item {self.steam_item_id}'} (in {self.collection})"

    @property
    def steam_url(self):
        """Generate the Steam Workshop URL for this item"""
        return (
            f"https://steamcommunity.com/workshop/filedetails/?id={self.steam_item_id}"
        )


def submission_file_upload_path(instance, filename):
    """Generate upload path for submission files"""
    # Create path: submissions/{submission_id}/{uuid}_{filename}
    ext = filename.split(".")[-1] if "." in filename else ""
    new_filename = f"{uuid.uuid4()}_{filename}" if ext else str(uuid.uuid4())
    return f"submissions/{instance.response.submission.id}/{new_filename}"


class Submission(models.Model):
    """Model representing a submission containing multiple puzzle responses"""

    # Identification
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)

    # User information (optional for anonymous submissions)
    user = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        help_text="User who made the submission (null for anonymous)",
    )

    # Submission metadata
    notes = models.TextField(
        null=True,
        blank=True,
        help_text="Optional notes about the submission",
    )

    # Status tracking
    is_validated = models.BooleanField(
        default=False, help_text="Whether this submission has been manually validated"
    )
    validated_by = models.ForeignKey(
        User,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="validated_submissions",
        help_text="Admin user who validated this submission",
    )
    validated_at = models.DateTimeField(
        null=True, blank=True, help_text="When this submission was validated"
    )

    # Manual validation request
    manual_validation_requested = models.BooleanField(
        default=False,
        help_text="Whether the user specifically requested manual validation",
    )

    # Timestamps
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-created_at"]
        verbose_name = "Submission"
        verbose_name_plural = "Submissions"

    def __str__(self):
        user_info = f"by {self.user.username}" if self.user else "anonymous"
        return f"Submission {self.id} {user_info}"

    @property
    def total_responses(self):
        """Get total number of puzzle responses in this submission"""
        return self.responses.count()

    @property
    def needs_validation(self):
        """Check if any response needs manual validation"""
        return self.responses.filter(needs_manual_validation=True).exists()


class PuzzleResponseQuerySet(models.QuerySet):
    def annotate_rank_points(self) -> Self:
        return (
            self.annotate(
                points=F("puzzle__points_factor__cost") * F("validated_cost")
                + F("puzzle__points_factor__cycles") * F("validated_cycles")
                + F("puzzle__points_factor__area") * F("validated_area")
            )
            .annotate(
                user_response_rank=Window(
                    expression=RowNumber(),
                    partition_by=[F("puzzle"), F("submission__user")],
                    order_by=F("points").asc(),
                )
            )
            # .filter(user_response_rank=1)
            .annotate(
                puzzle_user_rank=Window(
                    expression=Rank(),
                    partition_by=[F("puzzle")],
                    order_by=F("points").asc(),
                )
            )
            .annotate(
                rank_points=Cast(
                    JsonIndex(
                        F("puzzle__points_value__points"),
                        Cast(F("puzzle_user_rank") - 1, models.IntegerField()),
                    ),
                    models.IntegerField(),
                )
            )
        )

    def filter_user_best_response(self) -> Self:
        return self.annotate_rank_points().filter(user_response_rank=1)


class PuzzleResponseManager(models.Manager.from_queryset(PuzzleResponseQuerySet)):
    pass


class PuzzleResponse(models.Model):
    """Model representing a response/solution for a specific puzzle"""

    # Relationships
    submission = models.ForeignKey(
        Submission, on_delete=models.CASCADE, related_name="responses"
    )
    puzzle = models.ForeignKey(
        SteamCollectionItem,
        on_delete=models.CASCADE,
        related_name="responses",
        help_text="The puzzle this response is for",
    )

    # OCR extracted data
    puzzle_name = models.CharField(
        max_length=255, help_text="Puzzle name as detected by OCR"
    )
    cost = models.IntegerField(blank=True, help_text="Cost value from OCR")
    cycles = models.IntegerField(blank=True, help_text="Cycles value from OCR")
    area = models.IntegerField(blank=True, help_text="Area value from OCR")

    # Validation flags
    needs_manual_validation = models.BooleanField(
        default=False, help_text="Whether OCR failed and manual validation is needed"
    )

    ocr_confidence_cost = models.FloatField(
        null=True, blank=True, help_text="OCR confidence score for cost (0.0 to 1.0)"
    )
    ocr_confidence_cycles = models.FloatField(
        null=True, blank=True, help_text="OCR confidence score for cycles (0.0 to 1.0)"
    )
    ocr_confidence_area = models.FloatField(
        null=True, blank=True, help_text="OCR confidence score for area (0.0 to 1.0)"
    )

    # Manual validation overrides
    validated_cost = models.IntegerField(
        null=True, blank=True, help_text="Manually validated cost value"
    )
    validated_cycles = models.IntegerField(
        null=True, blank=True, help_text="Manually validated cycles value"
    )
    validated_area = models.IntegerField(
        null=True, blank=True, help_text="Manually validated area value"
    )

    # Timestamps
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    objects = PuzzleResponseManager()

    class Meta:
        ordering = ["submission", "puzzle__order_index"]
        verbose_name = "Puzzle Response"
        verbose_name_plural = "Puzzle Responses"
        _base_manager_name = "objects"

    def __str__(self):
        return f"Response for {self.puzzle_name} in {self.submission}"

    @property
    def final_cost(self):
        """Get the final cost value (validated if available, otherwise OCR)"""
        return self.validated_cost or self.cost

    @property
    def final_cycles(self):
        """Get the final cycles value (validated if available, otherwise OCR)"""
        return self.validated_cycles or self.cycles

    @property
    def final_area(self):
        """Get the final area value (validated if available, otherwise OCR)"""
        return self.validated_area or self.area

    def mark_for_validation(self, reason="OCR failed"):
        """Mark this response as needing manual validation"""
        self.needs_manual_validation = True
        self.save(update_fields=["needs_manual_validation"])


class SubmissionFile(models.Model):
    """Model representing files uploaded with a puzzle response"""

    # Relationships
    response = models.ForeignKey(
        PuzzleResponse, on_delete=models.CASCADE, related_name="files"
    )

    # File information
    file = models.FileField(
        upload_to=submission_file_upload_path, help_text="Uploaded file (image/gif)"
    )
    original_filename = models.CharField(
        max_length=255, help_text="Original filename as uploaded by user"
    )
    file_size = models.PositiveIntegerField(help_text="File size in bytes")
    content_type = models.CharField(max_length=100, help_text="MIME type of the file")

    # OCR metadata
    ocr_processed = models.BooleanField(
        default=False, help_text="Whether OCR has been processed for this file"
    )
    ocr_raw_data = models.JSONField(
        null=True, blank=True, help_text="Raw OCR data as JSON"
    )
    ocr_error = models.TextField(blank=True, help_text="OCR processing error message")

    # Timestamps
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["response", "created_at"]
        verbose_name = "Submission File"
        verbose_name_plural = "Submission Files"

    def __str__(self):
        return f"{self.original_filename} for {self.response}"

    @property
    def file_url(self):
        """Get the URL for the uploaded file"""
        if self.file:
            return self.file.url
        return None

    def save(self, *args, **kwargs):
        # Set file metadata on save
        if self.file and not self.file_size:
            self.file_size = self.file.size
        super().save(*args, **kwargs)
