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 ReplayConsolidationOutcome, ReplayEventType, ReplayExecutionStatus


class ReplayFixtureExecution(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    replay_order = models.OneToOneField(
        "match_result_governance.MatchReplayOrder", on_delete=models.PROTECT,
        related_name="execution",
    )
    root_match = models.ForeignKey(
        "matches.Match", on_delete=models.PROTECT, related_name="root_replay_executions"
    )
    original_match = models.ForeignKey(
        "matches.Match", on_delete=models.PROTECT, related_name="outgoing_replay_executions"
    )
    original_fixture = models.ForeignKey(
        "matches.Fixture", on_delete=models.PROTECT, related_name="outgoing_replay_executions"
    )
    replacement_fixture = models.OneToOneField(
        "matches.Fixture", on_delete=models.PROTECT, related_name="replay_execution"
    )
    replacement_match = models.OneToOneField(
        "matches.Match", on_delete=models.PROTECT, related_name="replay_execution"
    )
    parent_execution = models.ForeignKey(
        "self", null=True, blank=True, on_delete=models.PROTECT,
        related_name="child_executions"
    )
    replay_sequence = models.PositiveSmallIntegerField()
    status = models.CharField(max_length=24, choices=ReplayExecutionStatus.choices)
    draw_ratification_hash = models.CharField(max_length=64, blank=True, default="")
    calendar_edition = models.ForeignKey(
        "competition_calendar_governance.CompetitionCalendarEdition",
        null=True, blank=True, on_delete=models.PROTECT, related_name="replay_executions"
    )
    execution_payload = models.JSONField(default=dict)
    execution_hash = models.CharField(max_length=64)
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.PROTECT,
        related_name="created_replay_executions"
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["root_match_id", "replay_sequence", "created_at"]
        constraints = [
            models.CheckConstraint(condition=Q(replay_sequence__gte=1), name="replayexec_sequence_pos"),
            models.CheckConstraint(condition=~Q(execution_hash=""), name="replayexec_hash_nonempty"),
        ]

    def save(self, *args, **kwargs):
        if self.pk and type(self).objects.filter(pk=self.pk).exists():
            original = type(self).objects.get(pk=self.pk)
            immutable = [
                "replay_order_id", "root_match_id", "original_match_id", "original_fixture_id",
                "replacement_fixture_id", "replacement_match_id", "parent_execution_id",
                "replay_sequence", "draw_ratification_hash", "execution_payload", "execution_hash",
                "created_by_id", "created_at",
            ]
            if any(getattr(original, name) != getattr(self, name) for name in immutable):
                raise ValidationError("Replay execution evidence is immutable.")
            if original.status == ReplayExecutionStatus.CONSOLIDATED and self.status != original.status:
                raise ValidationError("Consolidated replay execution cannot leave CONSOLIDATED state.")
        return super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        raise ValidationError("Replay executions cannot be deleted.")


class ReplayResultConsolidation(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    execution = models.OneToOneField(
        ReplayFixtureExecution, on_delete=models.PROTECT, related_name="consolidation"
    )
    root_match = models.ForeignKey(
        "matches.Match", on_delete=models.PROTECT, related_name="replay_consolidations"
    )
    direct_replacement_match = models.ForeignKey(
        "matches.Match", on_delete=models.PROTECT, related_name="direct_replay_consolidations"
    )
    terminal_match = models.ForeignKey(
        "matches.Match", on_delete=models.PROTECT, related_name="terminal_replay_consolidations"
    )
    terminal_certification = models.ForeignKey(
        "match_result_governance.MatchResultCertification", on_delete=models.PROTECT,
        related_name="replay_consolidations"
    )
    outcome = models.CharField(max_length=20, choices=ReplayConsolidationOutcome.choices)
    effective_home_score = models.PositiveSmallIntegerField(null=True, blank=True)
    effective_away_score = models.PositiveSmallIntegerField(null=True, blank=True)
    lineage_depth = models.PositiveSmallIntegerField(default=1)
    lineage_payload = models.JSONField(default=dict)
    consolidation_hash = models.CharField(max_length=64)
    consolidated_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.PROTECT,
        related_name="replay_result_consolidations"
    )
    consolidated_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        constraints = [
            models.CheckConstraint(condition=Q(lineage_depth__gte=1), name="replaycon_depth_pos"),
            models.CheckConstraint(condition=~Q(consolidation_hash=""), name="replaycon_hash_nonempty"),
        ]

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

    def delete(self, *args, **kwargs):
        raise ValidationError("Replay result consolidations cannot be deleted.")


class ReplayExecutionEvent(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    execution = models.ForeignKey(
        ReplayFixtureExecution, on_delete=models.PROTECT, related_name="events"
    )
    sequence = models.PositiveIntegerField()
    event_type = models.CharField(max_length=32, choices=ReplayEventType.choices)
    reference_type = models.CharField(max_length=64, blank=True, default="")
    reference_id = models.CharField(max_length=64, blank=True, default="")
    payload = models.JSONField(default=dict, blank=True)
    previous_hash = models.CharField(max_length=64, blank=True, default="")
    event_hash = models.CharField(max_length=64)
    actor = models.ForeignKey(
        settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.PROTECT,
        related_name="replay_execution_events"
    )
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["execution_id", "sequence"]
        constraints = [
            models.UniqueConstraint(fields=["execution", "sequence"], name="replayevent_seq_uq"),
            models.CheckConstraint(condition=Q(sequence__gte=1), name="replayevent_seq_pos"),
            models.CheckConstraint(condition=~Q(event_hash=""), name="replayevent_hash_nonempty"),
        ]

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