import uuid

from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import Q

from .constants import (
    CompetitionArchiveType,
    HallOfFameEntityType,
    HistoricalEntityType,
    HistoricalRecordMetric,
    HistoricalRecordRelation,
    HonorType,
    ProjectionSourceType,
)


class _ImmutableQuerySet(models.QuerySet):
    def update(self, **kwargs):
        raise ValidationError("Official historical records are immutable.")

    def delete(self):
        raise ValidationError("Official historical records cannot be deleted.")


class _ImmutableModel(models.Model):
    objects = _ImmutableQuerySet.as_manager()

    class Meta:
        abstract = True

    def save(self, *args, **kwargs):
        if self.pk and type(self).objects.filter(pk=self.pk).exists():
            raise ValidationError("Official historical records are immutable.")
        return super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        raise ValidationError("Official historical records cannot be deleted.")


class OfficialHistoryProjection(_ImmutableModel):
    """One immutable materialized view of the effective ratified season record.

    A new projection is appended whenever the source official-record hash chain
    advances. Old projections remain queryable and verifiable forever.
    """

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    ratification = models.ForeignKey(
        "season_ratification.SeasonRatificationRecord",
        on_delete=models.PROTECT,
        related_name="history_projections",
    )
    season = models.ForeignKey(
        "seasons.Season",
        on_delete=models.PROTECT,
        related_name="official_history_projections",
    )
    sequence = models.PositiveIntegerField()
    source_type = models.CharField(max_length=16, choices=ProjectionSourceType.choices)
    source_chain_head_hash = models.CharField(max_length=64)
    source_snapshot_hash = models.CharField(max_length=64)
    history_context_hash = models.CharField(max_length=64)
    schema_version = models.PositiveSmallIntegerField(default=1)
    projection_payload = models.JSONField(default=dict)
    projection_hash = models.CharField(max_length=64)
    source_event_id = models.UUIDField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["season_id", "sequence"]
        constraints = [
            models.UniqueConstraint(fields=["ratification", "sequence"], name="official_hist_projection_seq_uq"),
            models.UniqueConstraint(
                fields=["ratification", "source_chain_head_hash", "history_context_hash"],
                name="official_hist_projection_source_uq",
            ),
            models.CheckConstraint(condition=Q(sequence__gte=1), name="official_hist_projection_seq_pos"),
            models.CheckConstraint(condition=Q(schema_version__gte=1), name="official_hist_projection_schema_pos"),
            models.CheckConstraint(condition=~Q(projection_hash=""), name="official_hist_projection_hash_nonempty"),
        ]
        indexes = [
            models.Index(fields=["season", "sequence"], name="official_hist_season_seq_idx"),
            models.Index(fields=["source_chain_head_hash"], name="official_hist_chain_hash_idx"),
        ]


class OfficialCompetitionArchive(_ImmutableModel):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    projection = models.ForeignKey(
        OfficialHistoryProjection, on_delete=models.PROTECT, related_name="competitions"
    )
    archive_type = models.CharField(max_length=16, choices=CompetitionArchiveType.choices)
    source_id = models.CharField(max_length=64)
    label = models.CharField(max_length=180)
    competition_type = models.CharField(max_length=32, blank=True, default="")
    competition_kind = models.CharField(max_length=48, blank=True, default="")
    country = models.CharField(max_length=100, blank=True, default="")
    level = models.PositiveSmallIntegerField(null=True, blank=True)
    status = models.CharField(max_length=24, blank=True, default="")
    champion_club = models.ForeignKey(
        "clubs.Club", null=True, blank=True, on_delete=models.PROTECT, related_name="official_championship_archives"
    )
    runner_up_club = models.ForeignKey(
        "clubs.Club", null=True, blank=True, on_delete=models.PROTECT, related_name="official_runner_up_archives"
    )
    champion_reference = models.CharField(max_length=80, blank=True, default="")
    runner_up_reference = models.CharField(max_length=80, blank=True, default="")
    standings_payload = models.JSONField(default=dict, blank=True)
    source_evidence_hash = models.CharField(max_length=64, blank=True, default="")
    archive_hash = models.CharField(max_length=64)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["projection_id", "archive_type", "source_id"]
        constraints = [
            models.UniqueConstraint(fields=["projection", "archive_type", "source_id"], name="official_comp_archive_uq"),
            models.CheckConstraint(condition=~Q(archive_hash=""), name="official_comp_archive_hash_nonempty"),
        ]
        indexes = [models.Index(fields=["archive_type", "source_id"], name="official_comp_source_idx")]


class OfficialLeagueStandingRow(_ImmutableModel):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    projection = models.ForeignKey(
        OfficialHistoryProjection, on_delete=models.PROTECT, related_name="standing_rows"
    )
    competition_archive = models.ForeignKey(
        OfficialCompetitionArchive, on_delete=models.PROTECT, related_name="standing_rows"
    )
    position = models.PositiveSmallIntegerField()
    club = models.ForeignKey(
        "clubs.Club", on_delete=models.PROTECT, related_name="official_historical_standings"
    )
    club_name_snapshot = models.CharField(max_length=160)
    played = models.PositiveSmallIntegerField(default=0)
    won = models.PositiveSmallIntegerField(default=0)
    drawn = models.PositiveSmallIntegerField(default=0)
    lost = models.PositiveSmallIntegerField(default=0)
    goals_for = models.PositiveSmallIntegerField(default=0)
    goals_against = models.PositiveSmallIntegerField(default=0)
    goal_difference = models.IntegerField(default=0)
    points = models.IntegerField(default=0)
    unresolved_tie = models.BooleanField(default=False)
    row_payload = models.JSONField(default=dict)
    row_hash = models.CharField(max_length=64)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["competition_archive_id", "position"]
        constraints = [
            models.UniqueConstraint(fields=["competition_archive", "position"], name="official_standing_position_uq"),
            models.UniqueConstraint(fields=["competition_archive", "club"], name="official_standing_club_uq"),
            models.CheckConstraint(condition=Q(position__gte=1), name="official_standing_position_pos"),
            models.CheckConstraint(condition=~Q(row_hash=""), name="official_standing_hash_nonempty"),
        ]


class OfficialMatchArchive(_ImmutableModel):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    projection = models.ForeignKey(
        OfficialHistoryProjection, on_delete=models.PROTECT, related_name="matches"
    )
    match = models.ForeignKey(
        "matches.Match", on_delete=models.PROTECT, related_name="official_history_entries"
    )
    competition_archive_type = models.CharField(max_length=16, choices=CompetitionArchiveType.choices)
    competition_source_id = models.CharField(max_length=64)
    round_number = models.PositiveSmallIntegerField()
    leg = models.PositiveSmallIntegerField()
    competition_type = models.CharField(max_length=16)
    home_club = models.ForeignKey(
        "clubs.Club", on_delete=models.PROTECT, related_name="official_home_match_archives"
    )
    away_club = models.ForeignKey(
        "clubs.Club", on_delete=models.PROTECT, related_name="official_away_match_archives"
    )
    home_club_name_snapshot = models.CharField(max_length=160)
    away_club_name_snapshot = models.CharField(max_length=160)
    status = models.CharField(max_length=16)
    result_type = models.CharField(max_length=20)
    home_score = models.PositiveSmallIntegerField(null=True, blank=True)
    away_score = models.PositiveSmallIntegerField(null=True, blank=True)
    completed_at = models.DateTimeField(null=True, blank=True)
    simulation_report_id = models.UUIDField(null=True, blank=True)
    simulation_report_hash = models.CharField(max_length=64, blank=True, default="")
    analytics_run_id = models.UUIDField(null=True, blank=True)
    analytics_output_hash = models.CharField(max_length=64, blank=True, default="")
    official_match_report_id = models.UUIDField(null=True, blank=True)
    official_match_report_hash = models.CharField(max_length=64, blank=True, default="")
    official_match_report_ratification_hash = models.CharField(max_length=64, blank=True, default="")
    result_certification_id = models.UUIDField(null=True, blank=True)
    result_certification_hash = models.CharField(max_length=64, blank=True, default="")
    result_certification_outcome = models.CharField(max_length=24, blank=True, default="")
    replay_lineage_role = models.CharField(max_length=24, blank=True, default="NONE")
    replay_execution_id = models.UUIDField(null=True, blank=True)
    replay_consolidation_id = models.UUIDField(null=True, blank=True)
    replay_consolidation_hash = models.CharField(max_length=64, blank=True, default="")
    statistics_countable = models.BooleanField(default=True)
    evidence_payload = models.JSONField(default=dict)
    archive_hash = models.CharField(max_length=64)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["projection_id", "competition_archive_type", "competition_source_id", "round_number", "match_id"]
        constraints = [
            models.UniqueConstraint(fields=["projection", "match"], name="official_match_archive_uq"),
            models.CheckConstraint(condition=~Q(archive_hash=""), name="official_match_archive_hash_nonempty"),
        ]
        indexes = [
            models.Index(fields=["match"], name="official_match_id_idx"),
            models.Index(fields=["competition_archive_type", "competition_source_id"], name="official_match_comp_idx"),
        ]


class OfficialClubSeasonStatistic(_ImmutableModel):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    projection = models.ForeignKey(
        OfficialHistoryProjection, on_delete=models.PROTECT, related_name="club_statistics"
    )
    club = models.ForeignKey(
        "clubs.Club", on_delete=models.PROTECT, related_name="official_season_statistics"
    )
    club_name_snapshot = models.CharField(max_length=160)
    matches = models.PositiveIntegerField(default=0)
    wins = models.PositiveIntegerField(default=0)
    draws = models.PositiveIntegerField(default=0)
    losses = models.PositiveIntegerField(default=0)
    goals_for = models.PositiveIntegerField(default=0)
    goals_against = models.PositiveIntegerField(default=0)
    clean_sheets = models.PositiveIntegerField(default=0)
    yellow_cards = models.PositiveIntegerField(default=0)
    red_cards = models.PositiveIntegerField(default=0)
    competition_breakdown = models.JSONField(default=dict)
    statistic_payload = models.JSONField(default=dict)
    statistic_hash = models.CharField(max_length=64)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["projection_id", "club_id"]
        constraints = [
            models.UniqueConstraint(fields=["projection", "club"], name="official_club_season_stat_uq"),
            models.CheckConstraint(condition=~Q(statistic_hash=""), name="official_club_stat_hash_nonempty"),
        ]


class OfficialPlayerSeasonStatistic(_ImmutableModel):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    projection = models.ForeignKey(
        OfficialHistoryProjection, on_delete=models.PROTECT, related_name="player_statistics"
    )
    player = models.ForeignKey(
        "players.Player", on_delete=models.PROTECT, related_name="official_season_statistics"
    )
    player_name_snapshot = models.CharField(max_length=220)
    nationality_snapshot = models.CharField(max_length=100, blank=True, default="")
    position_snapshot = models.CharField(max_length=8, blank=True, default="")
    appearances = models.PositiveIntegerField(default=0)
    starts = models.PositiveIntegerField(default=0)
    minutes = models.PositiveIntegerField(default=0)
    goals = models.PositiveIntegerField(default=0)
    assists = models.PositiveIntegerField(default=0)
    clean_sheets = models.PositiveIntegerField(default=0)
    yellow_cards = models.PositiveIntegerField(default=0)
    red_cards = models.PositiveIntegerField(default=0)
    motm_awards = models.PositiveIntegerField(default=0)
    xg = models.DecimalField(max_digits=14, decimal_places=3, default=0)
    xa = models.DecimalField(max_digits=14, decimal_places=3, default=0)
    average_rating = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)
    club_breakdown = models.JSONField(default=dict)
    competition_breakdown = models.JSONField(default=dict)
    statistic_payload = models.JSONField(default=dict)
    statistic_hash = models.CharField(max_length=64)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["projection_id", "player_id"]
        constraints = [
            models.UniqueConstraint(fields=["projection", "player"], name="official_player_season_stat_uq"),
            models.CheckConstraint(condition=~Q(statistic_hash=""), name="official_player_stat_hash_nonempty"),
        ]
        indexes = [
            models.Index(fields=["player", "projection"], name="official_player_projection_idx"),
        ]


class OfficialHonor(_ImmutableModel):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    projection = models.ForeignKey(
        OfficialHistoryProjection, on_delete=models.PROTECT, related_name="honors"
    )
    honor_key = models.CharField(max_length=220)
    honor_type = models.CharField(max_length=32, choices=HonorType.choices)
    entity_type = models.CharField(max_length=16, choices=HistoricalEntityType.choices)
    club = models.ForeignKey(
        "clubs.Club", null=True, blank=True, on_delete=models.PROTECT, related_name="official_honors"
    )
    player = models.ForeignKey(
        "players.Player", null=True, blank=True, on_delete=models.PROTECT, related_name="official_honors"
    )
    entity_reference = models.CharField(max_length=80, blank=True, default="")
    entity_name_snapshot = models.CharField(max_length=220)
    competition_archive_type = models.CharField(max_length=16, choices=CompetitionArchiveType.choices, blank=True, default="")
    competition_source_id = models.CharField(max_length=64, blank=True, default="")
    rank = models.PositiveSmallIntegerField(default=1)
    metric_key = models.CharField(max_length=80, blank=True, default="")
    metric_value = models.DecimalField(max_digits=20, decimal_places=4, null=True, blank=True)
    evidence_payload = models.JSONField(default=dict)
    honor_hash = models.CharField(max_length=64)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["projection_id", "honor_type", "honor_key"]
        constraints = [
            models.UniqueConstraint(fields=["projection", "honor_key"], name="official_honor_key_uq"),
            models.CheckConstraint(condition=Q(rank__gte=1), name="official_honor_rank_pos"),
            models.CheckConstraint(condition=~Q(honor_hash=""), name="official_honor_hash_nonempty"),
        ]
        indexes = [
            models.Index(fields=["honor_type", "club"], name="official_honor_club_idx"),
            models.Index(fields=["honor_type", "player"], name="official_honor_player_idx"),
        ]


class OfficialHistoricalRecord(_ImmutableModel):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    projection = models.ForeignKey(
        OfficialHistoryProjection, on_delete=models.PROTECT, related_name="records"
    )
    metric_key = models.CharField(max_length=48, choices=HistoricalRecordMetric.choices)
    scope_key = models.CharField(max_length=120, default="GLOBAL")
    entity_type = models.CharField(max_length=16, choices=HistoricalEntityType.choices)
    club = models.ForeignKey(
        "clubs.Club", null=True, blank=True, on_delete=models.PROTECT, related_name="official_records"
    )
    player = models.ForeignKey(
        "players.Player", null=True, blank=True, on_delete=models.PROTECT, related_name="official_records"
    )
    entity_reference = models.CharField(max_length=80, blank=True, default="")
    entity_name_snapshot = models.CharField(max_length=220)
    value = models.DecimalField(max_digits=20, decimal_places=4)
    unit = models.CharField(max_length=32, blank=True, default="")
    relation = models.CharField(max_length=12, choices=HistoricalRecordRelation.choices)
    previous_record = models.ForeignKey(
        "self", null=True, blank=True, on_delete=models.PROTECT, related_name="successor_records"
    )
    evidence_payload = models.JSONField(default=dict)
    record_hash = models.CharField(max_length=64)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["metric_key", "scope_key", "projection__season_id", "id"]
        constraints = [
            models.UniqueConstraint(
                fields=["projection", "metric_key", "scope_key", "entity_type", "entity_reference"],
                name="official_record_projection_entity_uq",
            ),
            models.CheckConstraint(condition=~Q(record_hash=""), name="official_record_hash_nonempty"),
        ]
        indexes = [
            models.Index(fields=["metric_key", "scope_key", "value"], name="official_rec_metric_value_idx"),
        ]


class OfficialHallOfFameEntry(_ImmutableModel):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    entity_type = models.CharField(max_length=16, choices=HallOfFameEntityType.choices)
    club = models.OneToOneField(
        "clubs.Club", null=True, blank=True, on_delete=models.PROTECT, related_name="hall_of_fame_entry"
    )
    player = models.OneToOneField(
        "players.Player", null=True, blank=True, on_delete=models.PROTECT, related_name="hall_of_fame_entry"
    )
    entity_name_snapshot = models.CharField(max_length=220)
    inducted_season = models.ForeignKey(
        "seasons.Season", on_delete=models.PROTECT, related_name="hall_of_fame_inductions"
    )
    source_projection = models.ForeignKey(
        OfficialHistoryProjection, on_delete=models.PROTECT, related_name="hall_of_fame_inductions"
    )
    induction_reason = models.TextField()
    evidence_payload = models.JSONField(default=dict)
    evidence_hash = models.CharField(max_length=64)
    entry_hash = models.CharField(max_length=64)
    inducted_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name="official_hall_of_fame_inductions"
    )
    inducted_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-inducted_at", "id"]
        constraints = [
            models.CheckConstraint(
                condition=(
                    Q(entity_type=HallOfFameEntityType.CLUB, club__isnull=False, player__isnull=True)
                    | Q(entity_type=HallOfFameEntityType.PLAYER, player__isnull=False, club__isnull=True)
                ),
                name="official_hof_entity_consistent",
            ),
            models.CheckConstraint(condition=~Q(entry_hash=""), name="official_hof_entry_hash_nonempty"),
        ]
