"""ESI blueprints provider + local mirror.

corptools doesn't store ME/TE/runs, so we pull /characters/{id}/blueprints/ with the
owner's read_blueprints token (from AA's shared esi.Token store) and mirror it locally,
keyed by item_id (== corptools CharacterAsset.item_id, the stock-join key).
"""

import logging
from functools import lru_cache

from ...models import CharacterBlueprint, CorporationBlueprint

logger = logging.getLogger(__name__)

BLUEPRINTS_SCOPE = "esi-characters.read_blueprints.v1"
CORP_BLUEPRINTS_SCOPE = "esi-corporations.read_blueprints.v1"
COMPATIBILITY_DATE = "2025-12-16"


@lru_cache(maxsize=1)
def _esi():
    from esi.openapi_clients import ESIClientProvider

    from ... import __version__

    return ESIClientProvider(
        compatibility_date=COMPATIBILITY_DATE,
        ua_appname="aa-shop",
        ua_version=__version__,
        operations=[
            "GetCharactersCharacterIdBlueprints",
            "GetCorporationsCorporationIdBlueprints",
        ],
    )


def _field(row, name):
    return row[name] if isinstance(row, dict) else getattr(row, name)


def blueprint_token(character):
    """A valid esi.Token for this character carrying the blueprints scope, or None.

    `character` is a corptools CharacterAudit.
    """
    from esi.models import Token

    eve_char_id = character.character.character_id
    return (
        Token.objects.filter(character_id=eve_char_id)
        .require_scopes([BLUEPRINTS_SCOPE])
        .require_valid()
        .first()
    )


def fetch_blueprints(character) -> list:
    """Fetch this character's blueprints from ESI. Returns [] if no usable token."""
    token = blueprint_token(character)
    if token is None:
        return []
    eve_char_id = character.character.character_id
    return (
        _esi()
        .client.Character.GetCharactersCharacterIdBlueprints(
            character_id=eve_char_id, token=token
        )
        .results(use_etag=False)
    )


def store_blueprints(character, rows) -> int:
    """Upsert the mirror for one character from ESI-shaped rows; prune stale.

    Returns the number of rows stored.
    """
    seen = []
    for r in rows:
        item_id = _field(r, "item_id")
        CharacterBlueprint.objects.update_or_create(
            item_id=item_id,
            defaults={
                "character": character,
                "type_id": _field(r, "type_id"),
                "me": _field(r, "material_efficiency"),
                "te": _field(r, "time_efficiency"),
                "runs": _field(r, "runs"),
                "quantity": _field(r, "quantity"),
            },
        )
        seen.append(item_id)
    CharacterBlueprint.objects.filter(character=character).exclude(
        item_id__in=seen
    ).delete()
    return len(seen)


def refresh_character_blueprints(character) -> int:
    """Fetch from ESI + mirror. Returns rows stored (0 if no token)."""
    return store_blueprints(character, fetch_blueprints(character))


def corp_blueprint_token(corp_audit):
    """A valid esi.Token from a DIRECTOR of this corp carrying the corp blueprints
    scope, or None. `corp_audit` is a corptools CorporationAudit."""
    from corptools.models import CharacterRoles
    from esi.models import Token

    corp_id = corp_audit.corporation.corporation_id
    director_char_ids = list(
        CharacterRoles.objects.filter(
            director=True,
            character__character__corporation_id=corp_id,
        ).values_list("character__character__character_id", flat=True)
    )
    if not director_char_ids:
        return None
    return (
        Token.objects.filter(character_id__in=director_char_ids)
        .require_scopes([CORP_BLUEPRINTS_SCOPE])
        .require_valid()
        .first()
    )


def fetch_corp_blueprints(corp_audit) -> list:
    """Fetch this corporation's blueprints from ESI. Returns [] if no usable token,
    or if the ESI call fails — e.g. a director token that is locally valid but which
    ESI rejects (director left the corp / lost the role since the last corptools sync).
    """
    token = corp_blueprint_token(corp_audit)
    if token is None:
        return []
    corp_id = corp_audit.corporation.corporation_id
    try:
        return (
            _esi()
            .client.Corporation.GetCorporationsCorporationIdBlueprints(
                corporation_id=corp_id, token=token
            )
            .results(use_etag=False)
        )
    except Exception:
        logger.warning(
            "corp blueprints fetch failed for corp %s; treating as no data", corp_id
        )
        return []


def store_corp_blueprints(corp_audit, rows) -> int:
    """Upsert the corp mirror from ESI-shaped rows; prune stale. Returns rows stored."""
    seen = []
    for r in rows:
        item_id = _field(r, "item_id")
        CorporationBlueprint.objects.update_or_create(
            item_id=item_id,
            defaults={
                "corporation_audit": corp_audit,
                "type_id": _field(r, "type_id"),
                "me": _field(r, "material_efficiency"),
                "te": _field(r, "time_efficiency"),
                "runs": _field(r, "runs"),
                "quantity": _field(r, "quantity"),
            },
        )
        seen.append(item_id)
    CorporationBlueprint.objects.filter(corporation_audit=corp_audit).exclude(
        item_id__in=seen
    ).delete()
    return len(seen)


def refresh_corporation_blueprints(corp_audit) -> int:
    """Fetch from ESI + mirror. Returns rows stored (0 if no token)."""
    return store_corp_blueprints(corp_audit, fetch_corp_blueprints(corp_audit))
