"""ESI access helpers for aa-pootest.

The corporation structures endpoint (`GetCorporationsCorporationIdStructures`)
requires the `esi-corporations.read_structures.v1` scope AND a character that
holds the **Director** or **Station Manager** role in the corporation. ESI
enforces the role server-side, so a token from a non-director will return no
data / a 403.

Note on fuel: ESI's structures endpoint exposes the fuel *expiry time*
(`fuel_expires`), not an absolute fuel quantity. "Current fuel level" is
therefore reported as the number of days remaining until that expiry.

Note on "last update": the structures endpoint has no per-structure update
field, so we stamp each row with the time the data was pulled from ESI.
"""

import datetime as dt

from django.utils.timezone import now

from allianceauth.services.hooks import get_extension_logger

from .providers import esi

logger = get_extension_logger(__name__)


def _esi_value(obj, key):
    """Read a field from an ESI result that may be a dict or an object."""
    return obj.get(key) if isinstance(obj, dict) else getattr(obj, key, None)


def _to_datetime(value):
    """Coerce an ESI date-time (datetime or ISO string) to an aware datetime."""
    if value is None:
        return None
    if isinstance(value, str):
        try:
            value = dt.datetime.fromisoformat(value.replace("Z", "+00:00"))
        except ValueError:
            return None
    if isinstance(value, dt.datetime):
        if value.tzinfo is None:
            value = value.replace(tzinfo=dt.timezone.utc)
        return value
    return None


def _fuel_display(fuel_expires) -> str:
    """Return a human-readable fuel / days-remaining string from `fuel_expires`."""
    expires = _to_datetime(fuel_expires)
    if expires is None:
        return "Unknown (no fuel data)"
    days = (expires - now()).total_seconds() / 86400
    stamp = expires.strftime("%Y-%m-%d %H:%M UTC")
    if days <= 0:
        return f"Out of fuel (expired {stamp})"
    return f"{days:.1f} days remaining (expires {stamp})"


def get_corporation_id_for_character(character_id: int) -> int:
    """Return the corporation ID for a character (public ESI data, no token)."""
    character = esi.client.Character.GetCharactersCharacterId(
        character_id=character_id,
    ).result()
    return _esi_value(character, "corporation_id")


def fetch_corporation_structures(corporation_id: int, token) -> list:
    """Return details for every structure owned by a corporation.

    Each entry is a dict ready to render as one div per structure, with keys:
    ``system``, ``name``, ``type``, ``fuel`` and ``last_update``.

    Requires a token with `esi-corporations.read_structures.v1` from a Director
    or Station Manager of the corporation.
    """
    structures = esi.client.Corporation.GetCorporationsCorporationIdStructures(
        corporation_id=corporation_id,
        token=token,
    ).results()

    fetched_at = now()
    system_cache: dict = {}
    type_cache: dict = {}

    rows = []
    for structure in structures:
        structure_id = _esi_value(structure, "structure_id")
        name = _esi_value(structure, "name") or _resolve_structure_name(
            structure_id, token
        )
        rows.append(
            {
                "system": _resolve_system_name(
                    _esi_value(structure, "system_id"), system_cache
                ),
                "name": name or f"Structure {structure_id}",
                "type": _resolve_type_name(
                    _esi_value(structure, "type_id"), type_cache
                ),
                "fuel": _fuel_display(_esi_value(structure, "fuel_expires")),
                "last_update": fetched_at,
            }
        )

    return sorted(rows, key=lambda r: (r["system"], r["name"]))


def _resolve_structure_name(structure_id, token):
    """Resolve a structure's name via the universe endpoint, or None on error."""
    try:
        info = esi.client.Universe.GetUniverseStructuresStructureId(
            structure_id=structure_id,
            token=token,
        ).result()
        return _esi_value(info, "name")
    except Exception as exc:  # noqa: BLE001 - stale/offlined ids can 403/404
        logger.warning(
            "Could not resolve name for structure %s: %s", structure_id, exc
        )
        return None


def _resolve_system_name(system_id, cache: dict) -> str:
    """Resolve a solar system name (public ESI), cached per request."""
    if system_id in cache:
        return cache[system_id]
    name = None
    try:
        info = esi.client.Universe.GetUniverseSystemsSystemId(
            system_id=system_id,
        ).result()
        name = _esi_value(info, "name")
    except Exception as exc:  # noqa: BLE001
        logger.warning("Could not resolve system %s: %s", system_id, exc)
    cache[system_id] = name or f"System {system_id}"
    return cache[system_id]


def _resolve_type_name(type_id, cache: dict) -> str:
    """Resolve an inventory type name (e.g. Fortizar/Raitaru), cached per request."""
    if type_id in cache:
        return cache[type_id]
    name = None
    try:
        info = esi.client.Universe.GetUniverseTypesTypeId(
            type_id=type_id,
        ).result()
        name = _esi_value(info, "name")
    except Exception as exc:  # noqa: BLE001
        logger.warning("Could not resolve type %s: %s", type_id, exc)
    cache[type_id] = name or f"Type {type_id}"
    return cache[type_id]
