from __future__ import annotations

from django.db import transaction
from django.utils import timezone

from careers.retirement import RetirementPolicyError, canonical_hash, validate_inheritance_manifest
from .exceptions import PlayerLegacyAssetError, PlayerLegacyAssetIntegrityError
from .models import (
    PlayerLegacyAsset,
    PlayerLegacyAssetAssignment,
    PlayerLegacyAssetAssignmentType,
    PlayerLegacyAssetType,
)


def _owner_id_for_player(player):
    return player.career.user_id


def _assignment_payload(*, asset, event_type, retirement_case_id=None, from_player_id=None, to_player_id=None):
    return {
        "schema_version": 1,
        "asset_id": str(asset.pk),
        "asset_type": asset.asset_type,
        "level": int(asset.level),
        "event_type": event_type,
        "retirement_case_id": str(retirement_case_id) if retirement_case_id else None,
        "from_player_id": str(from_player_id) if from_player_id else None,
        "to_player_id": str(to_player_id) if to_player_id else None,
        "recorded_at": timezone.now().isoformat(),
    }


class PlayerLegacyAssetService:
    @staticmethod
    @transaction.atomic
    def grant(*, owner, origin_player, asset_type, level=1, metadata=None):
        from players.models import Player
        locked_player = (
            Player.objects.select_for_update().select_related("career")
            .filter(pk=getattr(origin_player, "pk", None)).first()
        )
        if locked_player is None or locked_player.career.status != "ACTIVE":
            raise PlayerLegacyAssetError("Legacy assets can only be granted to an active player career.")
        origin_player = locked_player
        if owner is None or owner.pk != _owner_id_for_player(origin_player):
            raise PlayerLegacyAssetError("Asset owner must own the origin player's career.")
        if asset_type not in PlayerLegacyAssetType.values:
            raise PlayerLegacyAssetError("Unsupported legacy asset type.")
        if asset_type == PlayerLegacyAssetType.TRAINING_FACILITY and PlayerLegacyAsset.objects.filter(
            assigned_player=origin_player, asset_type=PlayerLegacyAssetType.TRAINING_FACILITY
        ).exists():
            raise PlayerLegacyAssetError("A player can have only one dedicated training facility.")
        level = int(level)
        if not 1 <= level <= 10:
            raise PlayerLegacyAssetError("Asset level must be between 1 and 10.")
        asset = PlayerLegacyAsset.objects.create(
            owner=owner,
            origin_player=origin_player,
            assigned_player=origin_player,
            asset_type=asset_type,
            level=level,
            metadata=dict(metadata or {}),
        )
        payload = _assignment_payload(
            asset=asset,
            event_type=PlayerLegacyAssetAssignmentType.INITIAL_ASSIGNMENT,
            to_player_id=origin_player.pk,
        )
        PlayerLegacyAssetAssignment.objects.create(
            asset=asset,
            event_type=PlayerLegacyAssetAssignmentType.INITIAL_ASSIGNMENT,
            to_player=origin_player,
            evidence_payload=payload,
            evidence_hash=canonical_hash(payload),
        )
        from asset_registry.services import AssetRegistryService
        AssetRegistryService.register_player_legacy(asset)
        return asset

    @staticmethod
    def eligible_manifest_for_player(*, player):
        rows = []
        for asset in PlayerLegacyAsset.objects.filter(
            owner_id=_owner_id_for_player(player), assigned_player=player
        ).order_by("id"):
            rows.append({
                "asset_id": str(asset.pk),
                "asset_type": asset.asset_type,
                "level": int(asset.level),
                "metadata": dict(asset.metadata or {}),
            })
        try:
            return validate_inheritance_manifest(rows)
        except RetirementPolicyError as exc:
            raise PlayerLegacyAssetIntegrityError(str(exc)) from exc

    @staticmethod
    @transaction.atomic
    def release_for_retirement(*, retirement_case, player):
        if retirement_case.owner_id != _owner_id_for_player(player):
            raise PlayerLegacyAssetIntegrityError("Retirement owner does not own this player.")
        assets = list(
            PlayerLegacyAsset.objects.select_for_update()
            .filter(owner_id=retirement_case.owner_id, assigned_player=player)
            .order_by("id")
        )
        try:
            manifest = validate_inheritance_manifest([
                {
                    "asset_id": str(asset.pk),
                    "asset_type": asset.asset_type,
                    "level": int(asset.level),
                    "metadata": dict(asset.metadata or {}),
                }
                for asset in assets
            ])
        except RetirementPolicyError as exc:
            raise PlayerLegacyAssetIntegrityError(str(exc)) from exc
        for asset in assets:
            asset.assigned_player = None
            asset.save(update_fields=["assigned_player", "updated_at"])
            payload = _assignment_payload(
                asset=asset,
                event_type=PlayerLegacyAssetAssignmentType.RETIREMENT_RELEASE,
                retirement_case_id=retirement_case.pk,
                from_player_id=player.pk,
            )
            PlayerLegacyAssetAssignment.objects.create(
                asset=asset,
                retirement_case=retirement_case,
                event_type=PlayerLegacyAssetAssignmentType.RETIREMENT_RELEASE,
                from_player=player,
                evidence_payload=payload,
                evidence_hash=canonical_hash(payload),
            )
        return manifest

    @staticmethod
    @transaction.atomic
    def inherit_manifest(*, retirement_case, successor_player, manifest):
        if successor_player.career.user_id != retirement_case.owner_id:
            raise PlayerLegacyAssetIntegrityError("Successor player must belong to the retirement owner.")
        try:
            canonical = validate_inheritance_manifest(list(manifest or []))
        except RetirementPolicyError as exc:
            raise PlayerLegacyAssetIntegrityError(str(exc)) from exc
        inherited = []
        for item in canonical:
            asset = (
                PlayerLegacyAsset.objects.select_for_update()
                .filter(pk=item["asset_id"], owner_id=retirement_case.owner_id)
                .first()
            )
            if asset is None:
                raise PlayerLegacyAssetIntegrityError("Inheritance asset no longer exists for this owner.")
            if asset.assigned_player_id is not None:
                if asset.assigned_player_id == successor_player.pk:
                    inherited.append(asset)
                    continue
                raise PlayerLegacyAssetIntegrityError("Inheritance asset is already assigned to another player.")
            if asset.asset_type != item["asset_type"] or int(asset.level) != int(item["level"]):
                raise PlayerLegacyAssetIntegrityError("Inheritance asset no longer matches the archived manifest.")
            asset.assigned_player = successor_player
            asset.save(update_fields=["assigned_player", "updated_at"])
            payload = _assignment_payload(
                asset=asset,
                event_type=PlayerLegacyAssetAssignmentType.SUCCESSOR_INHERITANCE,
                retirement_case_id=retirement_case.pk,
                from_player_id=retirement_case.player_id,
                to_player_id=successor_player.pk,
            )
            PlayerLegacyAssetAssignment.objects.create(
                asset=asset,
                retirement_case=retirement_case,
                event_type=PlayerLegacyAssetAssignmentType.SUCCESSOR_INHERITANCE,
                from_player=retirement_case.player,
                to_player=successor_player,
                evidence_payload=payload,
                evidence_hash=canonical_hash(payload),
            )
            inherited.append(asset)
        return inherited
