"""Server-priced NPC negotiations and earned, once-per-season development."""

from datetime import timedelta
from decimal import Decimal

from django.db import transaction
from django.utils import timezone
from rest_framework.exceptions import ValidationError

from careers.retirement import canonical_hash
from club_payroll.models import PayrollObligation
from club_payroll.services import PayrollObligationService
from clubs.models import Club, StaffRenewalOffer, StaffSeasonReview
from clubs.operations import event

from .models import ClubStaffAssignment, StaffMember


def morale(assignment):
    plan = assignment.employment_plan
    if not plan:
        return {"score": 70, "unpaid": "0", "basis": "LEGACY_OR_VOLUNTARY"}
    unpaid = sum(
        (
            o.outstanding_amount
            for o in PayrollObligation.objects.filter(
                plan=plan, due_date__lte=timezone.localdate()
            ).exclude(status__in=["PAID", "WAIVED", "CANCELLED"])
        ),
        Decimal(0),
    )
    score = max(
        10,
        min(
            100,
            70
            + int(
                min(
                    Decimal(15),
                    (
                        plan.installment_amount / assignment.staff_member.minimum_salary
                        - 1
                    )
                    * 10,
                )
            )
            - (40 if unpaid else 0),
        ),
    )
    return {"score": score, "unpaid": str(unpaid), "basis": "SALARY_AND_ARREARS"}


def lock_assignment(assignment_id):
    ref = ClubStaffAssignment.objects.get(pk=assignment_id)
    Club.objects.select_for_update().get(pk=ref.club_id)
    StaffMember.objects.select_for_update().get(pk=ref.staff_member_id)
    return (
        ClubStaffAssignment.objects.select_for_update(of=("self",))
        .select_related("employment_plan", "staff_member", "club")
        .get(pk=assignment_id)
    )


@transaction.atomic
def propose(*, user, assignment_id, monthly_salary, extension_days):
    row = lock_assignment(assignment_id)
    from api_core.permissions import require_club_manager

    require_club_manager(user, row.club)
    if row.status != "ACTIVE" or not row.employment_plan_id or row.role == "HEAD_COACH":
        raise ValidationError(
            "Only an active salaried specialist can negotiate renewal."
        )
    plan = row.employment_plan
    today = timezone.localdate()
    if plan.end_date < today or not 30 <= extension_days <= 365:
        raise ValidationError("Renew before expiry, extending by 30..365 days.")
    StaffRenewalOffer.objects.filter(
        assignment=row,
        status__in=["PENDING", "COUNTERED"],
        expires_at__lte=timezone.now(),
    ).update(status="EXPIRED")
    if row.renewal_offers.filter(status__in=["PENDING", "COUNTERED"]).exists():
        raise ValidationError("Resolve the existing renewal offer first.")
    minimum = max(
        row.staff_member.minimum_salary, plan.installment_amount * Decimal("0.90")
    ).quantize(Decimal("0.00000001"))
    if monthly_salary <= 0:
        raise ValidationError("Salary must be positive.")
    offer = StaffRenewalOffer.objects.create(
        assignment=row,
        monthly_salary=max(monthly_salary, minimum),
        end_date=plan.end_date + timedelta(days=extension_days),
        status="COUNTERED" if monthly_salary < minimum else "PENDING",
        expires_at=timezone.now() + timedelta(days=7),
    )
    event(
        row.club,
        user,
        "STAFF_RENEWAL_OFFER",
        {
            "offer": str(offer.pk),
            "requested_salary": str(monthly_salary),
            "salary": str(offer.monthly_salary),
            "status": offer.status,
        },
    )
    return offer


@transaction.atomic
def respond(*, user, offer_id, action):
    ref = StaffRenewalOffer.objects.get(pk=offer_id)
    row = lock_assignment(ref.assignment_id)
    from api_core.permissions import require_club_manager

    require_club_manager(user, row.club)
    offer = StaffRenewalOffer.objects.select_for_update().get(pk=offer_id)
    if offer.status not in ["PENDING", "COUNTERED"]:
        return offer
    if action == "CANCEL":
        offer.status = "CANCELLED"
    elif action == "ACCEPT":
        if (
            row.status != "ACTIVE"
            or offer.expires_at <= timezone.now()
            or row.employment_plan.end_date < timezone.localdate()
        ):
            raise ValidationError("Renewal offer expired or employment ended.")
        plan = row.employment_plan
        # Materialize earned old-rate installments before changing only future terms.
        PayrollObligationService.generate_for_plan(
            plan=plan, up_to=timezone.localdate()
        )
        plan.refresh_from_db()
        if (
            PayrollObligation.objects.filter(
                plan=plan, due_date__lte=timezone.localdate()
            )
            .exclude(status__in=["PAID", "CANCELLED", "WAIVED"])
            .exists()
        ):
            raise ValidationError("Settle earned salary before renewing.")
        if (
            PayrollObligation.objects.filter(
                plan=plan, due_date__gt=timezone.localdate()
            )
            .exclude(status="CANCELLED")
            .exists()
        ):
            raise ValidationError(
                "Future payroll has already been booked; resolve it before changing rates."
            )
        from economy.models import Wallet

        wallet = Wallet.objects.get(club=row.club, wallet_type="CLUB")
        if wallet.available_balance < offer.monthly_salary:
            raise ValidationError("Club cannot cover the renewed installment.")
        plan.installment_amount = offer.monthly_salary
        plan.end_date = offer.end_date
        plan.status = "ACTIVE"
        plan.save(
            update_fields=["installment_amount", "end_date", "status", "updated_at"]
        )
        offer.status = "ACCEPTED"
    else:
        raise ValidationError("Choose ACCEPT or CANCEL.")
    offer.save(update_fields=["status"])
    event(
        row.club,
        user,
        "STAFF_RENEWAL_DECIDED",
        {
            "offer": str(offer.pk),
            "status": offer.status,
            "salary": str(offer.monthly_salary),
            "end_date": str(offer.end_date),
        },
    )
    return offer


@transaction.atomic
def review(*, assignment_id, season):

    from .services import StaffCapabilityService

    row = lock_assignment(assignment_id)
    existing = StaffSeasonReview.objects.filter(
        season=season, assignment__staff_member=row.staff_member
    ).first()
    if existing:
        if canonical_hash(existing.evidence) != existing.evidence_hash:
            raise ValidationError("Staff review evidence is invalid.")
        return existing
    if season.status != "COMPLETED":
        raise ValidationError("Season must be completed before staff development.")
    from .models import StaffMatchEffect

    # Only actual immutable kickoff evidence qualifies: unpaid, expired or
    # unassigned staff never earn growth from another employee's appearances.
    matches = StaffMatchEffect.objects.filter(
        match__fixture__competition__season=season,
        match__status="COMPLETED",
        payload__staff__contains=[{"member_id": str(row.staff_member_id)}],
    ).count()
    profile = row.staff_member.capability
    before = {
        k: getattr(profile, k)
        for k in [
            "level",
            "leadership",
            "tactical",
            "development",
            "fitness",
            "goalkeeping",
        ]
    }
    satisfaction = morale(row)
    growth = 1 if matches >= 5 and satisfaction["score"] >= 60 else 0
    after = {k: min(100, v + growth) if k != "level" else v for k, v in before.items()}
    if growth:
        StaffCapabilityService.update(staff_member_id=row.staff_member_id, **after)
    evidence = {
        "assignment_id": str(row.pk),
        "season_id": str(season.pk),
        "matches": matches,
        "morale": satisfaction,
        "before": before,
        "after": after,
        "policy": "ftg.staff.earned-growth.v1",
    }
    return StaffSeasonReview.objects.create(
        assignment=row,
        season=season,
        evidence=evidence,
        evidence_hash=canonical_hash(evidence),
    )


def review_season(season):
    # Include departed staff; a move does not erase earned professional service.
    member_ids = set()
    results = []
    for row in ClubStaffAssignment.objects.filter(
        started_at__date__lte=season.end_date
    ).order_by("staff_member_id", "-started_at"):
        if row.staff_member_id in member_ids:
            continue
        member_ids.add(row.staff_member_id)
        results.append(review(assignment_id=row.pk, season=season))
    return results
