# Standard Library
import typing
from typing import Any, Literal

# Third Party
from httpx import Response
from pydantic import BaseModel

# Alliance Auth (Core)
from allianceauth.services.hooks import get_extension_logger
from esi.models import Token
from esi.openapi_clients import ESIClientProvider

if typing.TYPE_CHECKING:
    from esi.stubs import (
        CharacterID,
        CharactersCharacterIdFleetGet,
        CharactersCharacterIdImplantsGet,
        CharactersCharacterIdSearchGet,
        FleetsFleetIdWingsGet,
        FleetsFleetIdMembersGet,
        IncursionsGet,
    )

from . import __esi_compatibility_date__, __title__, __url__, __version__

logger = get_extension_logger(__name__)


esi = ESIClientProvider(
    compatibility_date=__esi_compatibility_date__,
    ua_appname=__title__,
    ua_version=__version__,
    ua_url=__url__,
    operations=[
        "GetIncursions",
        "GetFleetsFleetIdWings",
        "GetFleetsFleetIdMembers",
        "DeleteFleetsFleetIdMembersMemberId",
        "GetCharactersCharacterIdSearch",
        "PostUiOpenwindowInformation",
        "GetCharactersCharacterIdImplants",
        "GetCharactersCharacterIdFleet",
        "PostFleetsFleetIdMembers",
    ]
)

READ_FLEET_SCOPE = "esi-fleets.read_fleet.v1"
WRITE_FLEET_SCOPE = "esi-fleets.write_fleet.v1"
OPEN_WINDOW_SCOPE = "esi-ui.open_window.v1"
SEARCH_SCOPE = "esi-search.search_structures.v1"
IMPLANTS_SCOPE = "esi-clones.read_implants.v1"


def _get_token(character_id: "CharacterID", scopes: list[str]) -> Token:
    """Fetch an ESI token ensuring scopes are valid.

    Raises and logs on failure, letting callers decide error policy.
    """
    token = Token.get_token(character_id, scopes)
    return token


def get_incursions_incursions() -> tuple["IncursionsGet", Response]:
    result, response = esi.client.Incursions.GetIncursions().result(return_response=True)
    return result, response


def get_characters_character_id_fleet(character_id: "CharacterID") -> "CharactersCharacterIdFleetGet":
    token = _get_token(character_id, [READ_FLEET_SCOPE])
    result = esi.client.Fleets.GetCharactersCharacterIdFleet(
        character_id=character_id,
        token=token
    ).result(use_etag=False)
    return result


def get_fleets_fleet_id_wings(boss_character_id: "CharacterID", fleet_id: int) -> "FleetsFleetIdWingsGet":
    token = _get_token(boss_character_id, [READ_FLEET_SCOPE])
    return esi.client.Fleets.GetFleetsFleetIdWings(
        fleet_id=fleet_id,
        token=token
    ).result(use_etag=False)


def get_fleet_members(boss_character_id: "CharacterID", fleet_id: int) -> "FleetsFleetIdMembersGet":
    token = _get_token(boss_character_id, [READ_FLEET_SCOPE])
    return esi.client.Fleets.GetFleetsFleetIdMembers(
        fleet_id=fleet_id, token=token
    ).result(use_etag=False)


def kick_fleet_member(boss_character_id: "CharacterID", fleet_id: int, character_id: "CharacterID") -> Any:
    token = _get_token(boss_character_id, [WRITE_FLEET_SCOPE])
    return esi.client.Fleets.DeleteFleetsFleetIdMembersMemberId(
        fleet_id=fleet_id,
        member_id=character_id,
        token=token,
    ).result()


def kick_all_fleet_members(boss_character_id: "CharacterID", fleet_id: int) -> int:
    """Kick every member from the given fleet. Returns number removed."""
    fleet_members = get_fleet_members(boss_character_id, fleet_id)
    count = len(fleet_members)
    for member in fleet_members:
        try:
            kick_fleet_member(boss_character_id, fleet_id, member.character_id)
        except Exception as ex:
            logger.debug(
                "kick failed for character %s in fleet %s: %s",
                member.character_id,
                fleet_id,
                ex,
            )
    return count


def search_esi(
    character_id: "CharacterID",
    search_string: str,
    search_categories: list[Literal['agent', 'alliance', 'character', 'constellation', 'corporation', 'faction', 'inventory_type', 'region', 'solar_system', 'station', 'structure']],
    strict: bool = False,
) -> tuple["CharactersCharacterIdSearchGet", Response]:
    token = _get_token(character_id, [SEARCH_SCOPE])
    if isinstance(search_categories, list):
        categories = ",".join(search_categories)
    else:
        categories = search_categories
    operation = esi.client.Search.GetCharactersCharacterIdSearch(
        character_id=character_id,
        categories=categories,
        search=search_string,
        strict=strict,
        token=token,
    )
    result, response = operation.result(return_response=True)
    return result, response


def open_window_information(character_id: "CharacterID", target_id: int) -> tuple[Any, Response]:
    token = _get_token(character_id, [OPEN_WINDOW_SCOPE])
    operation = esi.client.User_Interface.PostUiOpenwindowInformation(
        target_id=target_id,
        token=token
    )
    result, response = operation.result(return_response=True)
    return result, response


def get_character_implants(character_id: int) -> tuple["CharactersCharacterIdImplantsGet", Response]:
    token = _get_token(character_id, [IMPLANTS_SCOPE])
    operation = esi.client.Clones.GetCharactersCharacterIdImplants(
        character_id=character_id,
        token=token
    )
    result, response = operation.result(return_response=True, use_etag=False)  # Ariel Cache is fine, i dont want to store implants
    return result, response


class FleetInviteBody(BaseModel):  # PostFleetsFleetIdMembersOperationBody
    character_id: int
    role: Literal["fleet_commander", "wing_commander", "squad_commander", "squad_member"]
    squad_id: int | None
    wing_id: int | None


def invite_to_fleet(
    boss_character_id: int,
    fleet_id: int,
    character_id: int,
    squad_id: int | None,
    wing_id: int | None,
    role: Literal["fleet_commander", "wing_commander", "squad_commander", "squad_member"] = "squad_member",
) -> tuple[Any, Any]:
    token = _get_token(boss_character_id, [WRITE_FLEET_SCOPE])
    invitation = FleetInviteBody(
        character_id=character_id,
        role=role,
        squad_id=squad_id,
        wing_id=wing_id,
    )
    result, response = esi.client.Fleets.PostFleetsFleetIdMembers(
        body=invitation.model_dump(),  # Cant import the real Pydantic here, doing it manually.
        fleet_id=fleet_id,
        token=token
    ).result(return_response=True)
    return result, response
