import hashlib
import json
import uuid

from django.db import models
from django.db.models import Q

from .constants import (
    ASSIGNMENT_ACTIVE,
    ASSIGNMENT_ENDED,
    ASSIGNMENT_STATUSES,
    ASSIGN_EVENT_ASSIGNED,
    ASSIGN_EVENT_ENDED,
    ASSIGN_EVENT_TYPES,
    HEAD_COACH,
    MAX_LEVEL,
    MAX_RATING,
    MIN_LEVEL,
    MIN_RATING,
    SOURCE_CAREER,
    SOURCE_NPC,
    SOURCE_TYPES,
    SPECIALIST_ROLES,
    STAFF_POLICY_KEY,
    STAFF_POLICY_VERSION,
    STAFF_ROLES,
    STAFF_TIERS,
)
from .exceptions import StaffHistoryImmutableError


class StaffMember(models.Model):
    tier = models.CharField(max_length=16, choices=[(x,x) for x in ("AMATEUR","NORMAL","PROFESSIONAL","SUPERSTAR")], default="AMATEUR")
    minimum_salary = models.DecimalField(max_digits=18, decimal_places=2, default=5)
    catalog_key = models.CharField(max_length=80, unique=True, null=True, blank=True)
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    source_type = models.CharField(max_length=12, choices=[(x, x.title()) for x in SOURCE_TYPES])
    specialty = models.CharField(max_length=24, choices=[(x, x.replace("_", " ").title()) for x in STAFF_ROLES])
    coach_career = models.OneToOneField(
        "careers.Career", null=True, blank=True, on_delete=models.PROTECT, related_name="staff_identity"
    )
    first_name = models.CharField(max_length=100, blank=True, default="")
    last_name = models.CharField(max_length=100, blank=True, default="")
    nationality = models.CharField(max_length=100, blank=True, default="")
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["last_name", "first_name", "id"]
        constraints = [
            models.CheckConstraint(condition=Q(source_type__in=SOURCE_TYPES), name="staff_valid_source"),
            models.CheckConstraint(condition=Q(minimum_salary__gt=0), name="staff_min_salary_positive"),
            models.CheckConstraint(condition=Q(tier__in=STAFF_TIERS), name="staff_valid_tier"),
            models.CheckConstraint(condition=Q(specialty__in=STAFF_ROLES), name="staff_valid_specialty"),
            models.CheckConstraint(
                condition=(
                    Q(source_type=SOURCE_CAREER, specialty=HEAD_COACH, coach_career__isnull=False)
                    | Q(source_type=SOURCE_NPC, specialty__in=SPECIALIST_ROLES, coach_career__isnull=True)
                ),
                name="staff_source_specialty_ok",
            ),
            models.CheckConstraint(
                condition=(
                    Q(source_type=SOURCE_CAREER)
                    | (~Q(first_name="") & ~Q(last_name="") & ~Q(nationality=""))
                ),
                name="staff_npc_identity_nonempty",
            ),
        ]

    def __str__(self):
        if self.coach_career_id:
            return f"Head Coach {self.coach_career_id}"
        return f"{self.first_name} {self.last_name} ({self.specialty})"


class StaffCapabilityProfile(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    staff_member = models.OneToOneField(StaffMember, on_delete=models.PROTECT, related_name="capability")
    level = models.PositiveSmallIntegerField()
    leadership = models.PositiveSmallIntegerField()
    tactical = models.PositiveSmallIntegerField()
    development = models.PositiveSmallIntegerField()
    fitness = models.PositiveSmallIntegerField()
    goalkeeping = models.PositiveSmallIntegerField()
    revision = models.PositiveIntegerField(default=1)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        constraints = [
            models.CheckConstraint(condition=Q(level__gte=MIN_LEVEL, level__lte=MAX_LEVEL), name="staff_cap_level_range"),
            models.CheckConstraint(condition=Q(revision__gte=1), name="staff_cap_revision_gte1"),
            *[
                models.CheckConstraint(
                    condition=Q(**{f"{field}__gte": MIN_RATING, f"{field}__lte": MAX_RATING}),
                    name=f"staff_cap_{field[:8]}_range",
                )
                for field in ("leadership", "tactical", "development", "fitness", "goalkeeping")
            ],
        ]


class _ImmutableHistoryQuerySet(models.QuerySet):
    def update(self, **kwargs):
        raise StaffHistoryImmutableError("Staff history is immutable.")

    def delete(self):
        raise StaffHistoryImmutableError("Staff history is immutable.")


class StaffCapabilityEvent(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    staff_member = models.ForeignKey(StaffMember, on_delete=models.PROTECT, related_name="capability_events")
    revision = models.PositiveIntegerField()
    level = models.PositiveSmallIntegerField()
    leadership = models.PositiveSmallIntegerField()
    tactical = models.PositiveSmallIntegerField()
    development = models.PositiveSmallIntegerField()
    fitness = models.PositiveSmallIntegerField()
    goalkeeping = models.PositiveSmallIntegerField()
    created_at = models.DateTimeField(auto_now_add=True)

    objects = _ImmutableHistoryQuerySet.as_manager()

    class Meta:
        ordering = ["staff_member_id", "revision"]
        constraints = [
            models.UniqueConstraint(fields=["staff_member", "revision"], name="uq_staff_cap_event_rev"),
            models.CheckConstraint(condition=Q(level__gte=MIN_LEVEL, level__lte=MAX_LEVEL), name="staff_evt_level_range"),
            *[
                models.CheckConstraint(
                    condition=Q(**{f"{field}__gte": MIN_RATING, f"{field}__lte": MAX_RATING}),
                    name=f"staff_evt_{field[:8]}_range",
                )
                for field in ("leadership", "tactical", "development", "fitness", "goalkeeping")
            ],
        ]

    def save(self, *args, **kwargs):
        if not self._state.adding:
            raise StaffHistoryImmutableError("Staff history is immutable.")
        return super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        raise StaffHistoryImmutableError("Staff history is immutable.")


class ClubStaffAssignment(models.Model):
    employment_plan = models.OneToOneField("club_payroll.PayrollPlan", on_delete=models.PROTECT, null=True, blank=True, related_name="staff_assignment")
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    club = models.ForeignKey("clubs.Club", on_delete=models.PROTECT, related_name="staff_assignments")
    staff_member = models.ForeignKey(StaffMember, on_delete=models.PROTECT, related_name="club_assignments")
    role = models.CharField(max_length=24, choices=[(x, x.replace("_", " ").title()) for x in STAFF_ROLES])
    status = models.CharField(max_length=10, choices=[(ASSIGNMENT_ACTIVE, "Active"), (ASSIGNMENT_ENDED, "Ended")], default=ASSIGNMENT_ACTIVE)
    started_at = models.DateTimeField()
    ended_at = models.DateTimeField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["club_id", "role", "started_at"]
        constraints = [
            models.CheckConstraint(condition=Q(role__in=STAFF_ROLES), name="staff_assign_valid_role"),
            models.CheckConstraint(condition=Q(status__in=ASSIGNMENT_STATUSES), name="staff_assign_valid_status"),
            models.CheckConstraint(
                condition=(Q(status=ASSIGNMENT_ACTIVE, ended_at__isnull=True) | Q(status=ASSIGNMENT_ENDED, ended_at__isnull=False)),
                name="staff_assign_lifecycle",
            ),
            models.CheckConstraint(condition=Q(ended_at__isnull=True) | Q(ended_at__gte=models.F("started_at")), name="staff_assign_end_after_start"),
            models.UniqueConstraint(fields=["club", "role"], condition=Q(status=ASSIGNMENT_ACTIVE) & ~Q(role="ASSISTANT_COACH"), name="uq_active_staff_role"),
            models.UniqueConstraint(fields=["staff_member"], condition=Q(status=ASSIGNMENT_ACTIVE), name="uq_active_staff_member"),
        ]
        indexes = [models.Index(fields=["club", "status"], name="staff_club_status_idx")]


class ClubStaffAssignmentEvent(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    assignment = models.ForeignKey(ClubStaffAssignment, on_delete=models.PROTECT, related_name="events")
    event_type = models.CharField(max_length=12, choices=[(x, x.title()) for x in ASSIGN_EVENT_TYPES])
    previous_status = models.CharField(max_length=10, blank=True, default="")
    new_status = models.CharField(max_length=10, choices=[(ASSIGNMENT_ACTIVE, "Active"), (ASSIGNMENT_ENDED, "Ended")])
    created_at = models.DateTimeField(auto_now_add=True)

    objects = _ImmutableHistoryQuerySet.as_manager()

    class Meta:
        ordering = ["created_at", "id"]
        constraints = [
            models.UniqueConstraint(fields=["assignment", "event_type"], name="uq_staff_assign_event"),
            models.CheckConstraint(
                condition=(
                    Q(event_type=ASSIGN_EVENT_ASSIGNED, previous_status="", new_status=ASSIGNMENT_ACTIVE)
                    | Q(event_type=ASSIGN_EVENT_ENDED, previous_status=ASSIGNMENT_ACTIVE, new_status=ASSIGNMENT_ENDED)
                ),
                name="staff_assign_event_ok",
            ),
        ]

    def save(self, *args, **kwargs):
        if not self._state.adding:
            raise StaffHistoryImmutableError("Staff history is immutable.")
        return super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        raise StaffHistoryImmutableError("Staff history is immutable.")


class TeamTrainingStaffSnapshot(models.Model):
    """Immutable staff context used for one TEAM training execution."""

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    session = models.OneToOneField("training.TrainingSession", on_delete=models.PROTECT, related_name="staff_snapshot")
    club = models.ForeignKey("clubs.Club", on_delete=models.PROTECT, related_name="training_staff_snapshots")
    head_assignment = models.ForeignKey(ClubStaffAssignment, null=True, blank=True, on_delete=models.PROTECT, related_name="+")
    assistant_assignment = models.ForeignKey(ClubStaffAssignment, null=True, blank=True, on_delete=models.PROTECT, related_name="+")
    fitness_assignment = models.ForeignKey(ClubStaffAssignment, null=True, blank=True, on_delete=models.PROTECT, related_name="+")
    goalkeeper_assignment = models.ForeignKey(ClubStaffAssignment, null=True, blank=True, on_delete=models.PROTECT, related_name="+")
    target_attribute = models.CharField(max_length=40)
    multiplier = models.DecimalField(max_digits=8, decimal_places=6)
    context_payload = models.JSONField()
    context_hash = models.CharField(max_length=64)
    policy_key = models.CharField(max_length=100, default=STAFF_POLICY_KEY)
    policy_version = models.CharField(max_length=30, default=STAFF_POLICY_VERSION)
    created_at = models.DateTimeField(auto_now_add=True)

    objects = _ImmutableHistoryQuerySet.as_manager()

    class Meta:
        constraints = [
            models.CheckConstraint(condition=Q(multiplier__gte=0), name="staff_snap_multiplier_gte0"),
            models.CheckConstraint(condition=~Q(context_hash=""), name="staff_snap_hash_nonempty"),
            models.CheckConstraint(condition=~Q(policy_key=""), name="staff_snap_policy_nonempty"),
            models.CheckConstraint(condition=~Q(policy_version=""), name="staff_snap_version_nonempty"),
        ]

    @staticmethod
    def hash_payload(payload):
        raw = json.dumps(payload, sort_keys=True, separators=(",", ":"), default=str).encode("utf-8")
        return hashlib.sha256(raw).hexdigest()

    def save(self, *args, **kwargs):
        if not self._state.adding:
            raise StaffHistoryImmutableError("Staff training snapshots are immutable.")
        return super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        raise StaffHistoryImmutableError("Staff training snapshots are immutable.")


class StaffMatchEffect(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    match = models.ForeignKey("matches.Match", on_delete=models.PROTECT)
    club = models.ForeignKey("clubs.Club", on_delete=models.PROTECT)
    payload = models.JSONField()
    evidence_hash = models.CharField(max_length=64)
    created_at = models.DateTimeField(auto_now_add=True)
    objects = _ImmutableHistoryQuerySet.as_manager()

    class Meta:
        constraints = [models.UniqueConstraint(fields=["match", "club"], name="staff_effect_match_club_unique")]

    def save(self, *args, **kwargs):
        if not self._state.adding:
            raise StaffHistoryImmutableError("Match staff evidence is immutable.")
        return super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        raise StaffHistoryImmutableError("Match staff evidence is immutable.")
