from django.db import models
from django.db.models import F, Q
from django.utils import timezone

from .constants import SeasonStatus


class Season(models.Model):
    id = models.BigAutoField(primary_key=True)

    name = models.CharField(
        max_length=100,
        unique=True,
    )

    year = models.PositiveSmallIntegerField()

    start_date = models.DateField()

    end_date = models.DateField()

    status = models.CharField(
        max_length=12,
        choices=SeasonStatus.choices,
        default=SeasonStatus.DRAFT,
    )

    created_at = models.DateTimeField(
        auto_now_add=True,
    )

    updated_at = models.DateTimeField(
        auto_now=True,
    )

    activated_at = models.DateTimeField(
        null=True,
        blank=True,
    )

    completed_at = models.DateTimeField(
        null=True,
        blank=True,
    )

    archived_at = models.DateTimeField(
        null=True,
        blank=True,
    )

    class Meta:
        ordering = ["-start_date", "-id"]

        indexes = [
            models.Index(
                fields=["status"],
                name="season_status_idx",
            ),
            models.Index(
                fields=["start_date", "end_date"],
                name="season_dates_idx",
            ),
            models.Index(
                fields=["year"],
                name="season_year_idx",
            ),
        ]

        constraints = [
            models.CheckConstraint(
                condition=Q(start_date__lt=F("end_date")),
                name="season_start_before_end",
            ),
            models.CheckConstraint(
                condition=Q(year__gte=1900, year__lte=2200),
                name="season_year_1900_2200",
            ),
            models.CheckConstraint(
                condition=Q(status__in=SeasonStatus.values),
                name="season_valid_status",
            ),
            models.UniqueConstraint(
                fields=["year"],
                name="unique_season_year",
            ),
            models.UniqueConstraint(
                fields=["status"],
                condition=Q(status=SeasonStatus.ACTIVE),
                name="unique_active_season",
            ),
            models.CheckConstraint(
                condition=(
                    Q(
                        status__in=[
                            SeasonStatus.DRAFT,
                            SeasonStatus.UPCOMING,
                            SeasonStatus.CANCELLED,
                        ],
                        activated_at__isnull=True,
                        completed_at__isnull=True,
                        archived_at__isnull=True,
                    )
                    | Q(
                        status=SeasonStatus.ACTIVE,
                        activated_at__isnull=False,
                        completed_at__isnull=True,
                        archived_at__isnull=True,
                    )
                    | Q(
                        status=SeasonStatus.COMPLETED,
                        activated_at__isnull=False,
                        completed_at__isnull=False,
                        archived_at__isnull=True,
                    )
                    | Q(
                        status=SeasonStatus.ARCHIVED,
                        activated_at__isnull=False,
                        completed_at__isnull=False,
                        archived_at__isnull=False,
                    )
                ),
                name="season_lifecycle_timestamps",
            ),
            models.CheckConstraint(
                condition=(
                    Q(activated_at__isnull=True)
                    | Q(completed_at__isnull=True)
                    | Q(completed_at__gte=F("activated_at"))
                ),
                name="season_completed_after_activation",
            ),
            models.CheckConstraint(
                condition=(
                    Q(completed_at__isnull=True)
                    | Q(archived_at__isnull=True)
                    | Q(archived_at__gte=F("completed_at"))
                ),
                name="season_archived_after_completion",
            ),
        ]

    def __str__(self):
        return self.name

    @property
    def is_active(self):
        return self.status == SeasonStatus.ACTIVE

    @property
    def is_completed(self):
        return self.status == SeasonStatus.COMPLETED

    @property
    def is_archived(self):
        return self.status == SeasonStatus.ARCHIVED

    @property
    def is_upcoming(self):
        return self.status == SeasonStatus.UPCOMING

    @property
    def has_started(self):
        return timezone.localdate() >= self.start_date

    @property
    def has_ended(self):
        return timezone.localdate() > self.end_date


class SeasonAutomationPolicy(models.Model):
    """Explicit opt-in calendar. Existing historical seasons remain untouched."""

    season = models.OneToOneField(Season, on_delete=models.PROTECT, related_name="automation")
    registration_opens_at = models.DateTimeField()
    starts_at = models.DateTimeField()
    midseason_opens_at = models.DateTimeField()
    midseason_closes_at = models.DateTimeField()
    round_gap_days = models.PositiveSmallIntegerField(default=6)
    kickoff_time = models.TimeField(default="18:00")
    enabled = models.BooleanField(default=True)
    auto_successor = models.BooleanField(default=True)
    continental_enabled = models.BooleanField(default=True)
    operator = models.ForeignKey("users.User", on_delete=models.PROTECT)
    last_checked_at = models.DateTimeField(null=True, blank=True)
    last_result = models.JSONField(default=dict)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        constraints = [
            models.CheckConstraint(condition=Q(registration_opens_at__lt=F("starts_at")), name="season_auto_preseason_order"),
            models.CheckConstraint(condition=Q(starts_at__lt=F("midseason_opens_at")), name="season_auto_mid_start_order"),
            models.CheckConstraint(condition=Q(midseason_opens_at__lt=F("midseason_closes_at")), name="season_auto_mid_end_order"),
            models.CheckConstraint(condition=Q(round_gap_days__gte=3, round_gap_days__lte=30), name="season_auto_gap_range"),
        ]


# ---------------------------------------------------------------------------
# Season & competition orchestration evidence
# ---------------------------------------------------------------------------
import uuid

from .constants import (
    SeasonCalendarPhase,
    SeasonCalendarSourceType,
    SeasonOrchestrationStatus,
    SeasonOrchestrationStepKey,
    SeasonOrchestrationStepStatus,
)


class SeasonOrchestrationRun(models.Model):
    """Durable, resumable workflow binding one closed season to its successor.

    The run is mutable only as workflow state.  Every successful step carries
    hashed result evidence and the business artifacts created by the step are
    independently protected by their owning domains.
    """

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    source_season = models.ForeignKey(
        Season,
        on_delete=models.PROTECT,
        related_name="orchestration_runs_out",
    )
    target_season = models.ForeignKey(
        Season,
        on_delete=models.PROTECT,
        related_name="orchestration_runs_in",
    )
    policy_key = models.CharField(max_length=100)
    policy_version = models.CharField(max_length=30)
    schema_version = models.PositiveSmallIntegerField(default=1)
    status = models.CharField(
        max_length=12,
        choices=SeasonOrchestrationStatus.choices,
        default=SeasonOrchestrationStatus.PENDING,
    )
    current_step = models.CharField(
        max_length=32,
        choices=SeasonOrchestrationStepKey.choices,
        blank=True,
        default="",
    )
    attempt_count = models.PositiveIntegerField(default=0)
    request_payload = models.JSONField(default=dict)
    request_hash = models.CharField(max_length=64)
    result_payload = models.JSONField(default=dict)
    result_hash = models.CharField(max_length=64, blank=True, default="")
    error_code = models.CharField(max_length=120, blank=True, default="")
    error_message = models.CharField(max_length=1000, blank=True, default="")
    started_at = models.DateTimeField(null=True, blank=True)
    completed_at = models.DateTimeField(null=True, blank=True)
    failed_at = models.DateTimeField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-created_at", "id"]
        constraints = [
            models.UniqueConstraint(
                fields=["source_season", "target_season"],
                name="uq_season_orch_source_target",
            ),
            models.CheckConstraint(
                condition=~Q(source_season=models.F("target_season")),
                name="season_orch_source_target_diff",
            ),
            models.CheckConstraint(
                condition=Q(status__in=SeasonOrchestrationStatus.values),
                name="season_orch_valid_status",
            ),
            models.CheckConstraint(
                condition=Q(schema_version__gte=1),
                name="season_orch_schema_gte_1",
            ),
            models.CheckConstraint(
                condition=~Q(request_hash=""),
                name="season_orch_req_hash_nonempty",
            ),
        ]
        indexes = [
            models.Index(fields=["status", "updated_at"], name="season_orch_status_idx"),
            models.Index(fields=["source_season", "target_season"], name="season_orch_pair_idx"),
        ]

    def __str__(self):
        return f"Season orchestration {self.source_season_id} -> {self.target_season_id}"


class SeasonOrchestrationStep(models.Model):
    """Persisted checkpoint for one deterministic orchestration step."""

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    run = models.ForeignKey(
        SeasonOrchestrationRun,
        on_delete=models.PROTECT,
        related_name="steps",
    )
    step_key = models.CharField(max_length=32, choices=SeasonOrchestrationStepKey.choices)
    ordinal = models.PositiveSmallIntegerField()
    status = models.CharField(
        max_length=12,
        choices=SeasonOrchestrationStepStatus.choices,
        default=SeasonOrchestrationStepStatus.PENDING,
    )
    attempt_count = models.PositiveIntegerField(default=0)
    input_payload = models.JSONField(default=dict)
    input_hash = models.CharField(max_length=64)
    result_payload = models.JSONField(default=dict)
    result_hash = models.CharField(max_length=64, blank=True, default="")
    error_code = models.CharField(max_length=120, blank=True, default="")
    error_message = models.CharField(max_length=1000, blank=True, default="")
    started_at = models.DateTimeField(null=True, blank=True)
    completed_at = models.DateTimeField(null=True, blank=True)
    failed_at = models.DateTimeField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["run_id", "ordinal"]
        constraints = [
            models.UniqueConstraint(fields=["run", "step_key"], name="uq_season_orch_step_key"),
            models.UniqueConstraint(fields=["run", "ordinal"], name="uq_season_orch_step_ord"),
            models.CheckConstraint(condition=Q(ordinal__gte=1), name="season_orch_step_ord_gte_1"),
            models.CheckConstraint(
                condition=Q(status__in=SeasonOrchestrationStepStatus.values),
                name="season_orch_step_valid_status",
            ),
            models.CheckConstraint(condition=~Q(input_hash=""), name="season_orch_step_input_hash"),
        ]
        indexes = [
            models.Index(fields=["run", "status"], name="season_orch_step_status_idx"),
        ]

    def __str__(self):
        return f"{self.run_id}:{self.ordinal}:{self.step_key}"


class _ImmutableSeasonCalendarQuerySet(models.QuerySet):
    def update(self, **kwargs):
        from .exceptions import SeasonOrchestrationIntegrityError

        raise SeasonOrchestrationIntegrityError("Season calendar evidence is immutable.")

    def delete(self):
        from .exceptions import SeasonOrchestrationIntegrityError

        raise SeasonOrchestrationIntegrityError("Season calendar evidence is immutable.")


class SeasonCompetitionCalendarSlot(models.Model):
    """Date-level competition window; exact local kickoff time is intentionally separate."""

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    run = models.ForeignKey(
        SeasonOrchestrationRun,
        on_delete=models.PROTECT,
        related_name="calendar_slots",
        null=True,
        blank=True,
    )
    season = models.ForeignKey(
        Season,
        on_delete=models.PROTECT,
        related_name="competition_calendar_slots",
    )
    source_type = models.CharField(max_length=12, choices=SeasonCalendarSourceType.choices)
    league_competition = models.ForeignKey(
        "leagues.LeagueCompetition",
        on_delete=models.PROTECT,
        null=True,
        blank=True,
        related_name="season_calendar_slots",
    )
    federation_competition = models.ForeignKey(
        "federations.FederationCompetition",
        on_delete=models.PROTECT,
        null=True,
        blank=True,
        related_name="season_calendar_slots",
    )
    phase = models.CharField(max_length=24, choices=SeasonCalendarPhase.choices)
    sequence = models.PositiveSmallIntegerField()
    scheduled_date = models.DateField()
    metadata = models.JSONField(default=dict)
    evidence_hash = models.CharField(max_length=64)
    created_at = models.DateTimeField(auto_now_add=True)

    objects = _ImmutableSeasonCalendarQuerySet.as_manager()

    class Meta:
        ordering = ["scheduled_date", "source_type", "sequence", "id"]
        constraints = [
            models.UniqueConstraint(fields=["season", "league_competition", "phase", "sequence"], condition=Q(league_competition__isnull=False), name="uq_season_league_calendar"),
            models.CheckConstraint(condition=Q(sequence__gte=1), name="season_cal_seq_gte_1"),
            models.CheckConstraint(
                condition=Q(source_type__in=SeasonCalendarSourceType.values),
                name="season_cal_valid_source_type",
            ),
            models.CheckConstraint(
                condition=Q(phase__in=SeasonCalendarPhase.values),
                name="season_cal_valid_phase",
            ),
            models.CheckConstraint(
                condition=(
                    Q(
                        source_type=SeasonCalendarSourceType.LEAGUE,
                        league_competition__isnull=False,
                        federation_competition__isnull=True,
                    )
                    | Q(
                        source_type=SeasonCalendarSourceType.FEDERATION,
                        league_competition__isnull=True,
                        federation_competition__isnull=False,
                    )
                ),
                name="season_cal_source_shape",
            ),
            models.UniqueConstraint(
                fields=["run", "league_competition", "phase", "sequence"],
                condition=Q(league_competition__isnull=False),
                name="uq_season_cal_league_slot",
            ),
            models.UniqueConstraint(
                fields=["run", "federation_competition", "phase", "sequence"],
                condition=Q(federation_competition__isnull=False),
                name="uq_season_cal_fed_slot",
            ),
            models.CheckConstraint(condition=~Q(evidence_hash=""), name="season_cal_hash_nonempty"),
        ]
        indexes = [
            models.Index(fields=["season", "scheduled_date"], name="season_cal_date_idx"),
        ]

    def save(self, *args, **kwargs):
        if not self._state.adding:
            from .exceptions import SeasonOrchestrationIntegrityError

            raise SeasonOrchestrationIntegrityError("Season calendar evidence is immutable.")
        return super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        from .exceptions import SeasonOrchestrationIntegrityError

        raise SeasonOrchestrationIntegrityError("Season calendar evidence is immutable.")
