# Standard Library
import logging
from datetime import datetime, timedelta
from typing import Any

# Third Party
from discord import Colour, Embed
from routing.routing import route_check_edencom, route_check_triglavian, route_length

# Alliance Auth (External Libs)
from eve_sde.models import Constellation, Region, SolarSystem

from incursions import __branch__, __version__
from incursions.models.incursion import Focus, Incursion
from incursions.models.waitlist import Waitlist
from incursions.static_data import incursion_constellations

from .app_settings import get_site_url

logger = logging.getLogger(__name__)

# Polaris / Jove fallback constants
FALLBACK_CONSTELLATION_ID = 20000054
FALLBACK_REGION_SYSTEM_ID = 10000004  # region lookup uses system_id attr on Region model in current code base
FALLBACK_SYSTEM_ID = 30000380


def embed_base() -> Embed:
    """Return a base embed pre-populated with common branding fields."""
    embed = Embed(title="AA Incursions")
    embed.url = f"{get_site_url()}/incursions/"
    embed.set_thumbnail(url="https://images.evetech.net/types/2192/render?size=128")
    embed.set_footer(text=f"AA Incursions v{__version__}-{__branch__}: Developed by Ariel Rin")
    embed.timestamp = datetime.now()
    return embed


def _resolve_incursion_location(incursion: Incursion) -> tuple[Constellation, Region, SolarSystem]:
    """Resolve constellation, region and staging system with defensive fallbacks."""
    constellation: Constellation
    region: Region
    staging: SolarSystem
    try:
        constellation = incursion.constellation or Constellation.objects.get(
            constellation_id=FALLBACK_CONSTELLATION_ID
        )
    except Exception:  # broad safety
        constellation = Constellation.objects.first()  # type: ignore[assignment]
    try:
        # avoid style complaint about line break before binary operator
        if constellation.region:  # type: ignore[union-attr]
            region = constellation.region  # type: ignore[assignment]
        else:
            region = Region.objects.get(system_id=FALLBACK_REGION_SYSTEM_ID)
    except Exception:
        region = Region.objects.first()  # type: ignore[assignment]
    try:
        staging = incursion.staging_solar_system or SolarSystem.objects.get(id=FALLBACK_SYSTEM_ID)
    except Exception:
        staging = SolarSystem.objects.first()  # type: ignore[assignment]
    return constellation, region, staging  # type: ignore[return-value]


def _constellation_meta(name: str) -> dict[str, Any]:
    try:
        return incursion_constellations[name]
    except KeyError:
        logger.warning("Constellation %s missing in static_data", name)
        return {"Headquarter": "Unknown", "Assaults": [], "Vanguards": []}


def embed_established(incursion: Incursion) -> Embed:
    constellation, region, solar_system = _resolve_incursion_location(incursion)
    embed = embed_base()
    embed.colour = Colour.green()
    embed.title = f"New {incursion.security_string}: {region.name}/{constellation.name}"
    embed.description = (
        "PSA: Create and use an Insta-Dock Bookmark when warping to the new dock-up. "
        "Scout the route ahead including the dock-up station."
    )
    embed.add_field(name="Region", value=region.name, inline=True)
    embed.add_field(name="Constellation", value=constellation.name)
    meta = _constellation_meta(constellation.name)
    embed.add_field(name="Headquarters", value=meta.get("Headquarter", "Unknown"))
    embed.add_field(name="Assault", value=", ".join(meta.get("Assaults", [])), inline=True)
    embed.add_field(name="Vanguard", value=", ".join(meta.get("Vanguards", [])), inline=True)
    # Distance from current focus HQ
    try:
        focus_inc = Focus.get_solo().incursion
        if focus_inc and focus_inc.staging_solar_system:
            jumps = route_length(solar_system.system_id, focus_inc.staging_solar_system.system_id)
            embed.add_field(
                name=f"Jumps from last HQ {focus_inc.staging_solar_system.name}",
                value=str(jumps),
                inline=True,
            )
    except Exception:
        logger.debug("Unable to compute jumps from last HQ", exc_info=True)
    embed.add_field(name="Suggested Dockup", value="Not Implemented", inline=True)
    embed.add_field(name="Edencom In Spawn", value="Not Implemented", inline=True)
    embed.add_field(name="Trigs In Spawn", value="Not Implemented", inline=True)
    return embed


def embed_established_addendum(incursion: Incursion) -> Embed:
    constellation, region, solar_system = _resolve_incursion_location(incursion)
    embed = embed_base()
    embed.colour = Colour.blue()
    embed.title = f"{incursion.security_string}: {region.name}/{constellation.name}"
    ts = incursion.established_timestamp
    if ts is None:
        logger.debug("Incursion %s missing established_timestamp", incursion.pk)
        ts = datetime.now()
    embed.add_field(name="Spawn Time", value=f"<t:{int(ts.timestamp())}:R>", inline=True)
    if incursion.is_high_sec:
        delta_days = 3
    elif incursion.is_low_sec:
        delta_days = 1
    else:
        delta_days = 2
    embed.add_field(
        name="Mommy Time",
        value=f"<t:{int((ts + timedelta(days=delta_days)).timestamp())}:R>",
        inline=True,
    )
    embed.add_field(
        name="Despawn Window Starts",
        value=f"<t:{int((ts + timedelta(days=4)).timestamp())}:R>",
        inline=True,
    )
    embed.add_field(
        name="Despawn Window Ends",
        value=f"<t:{int((ts + timedelta(days=8)).timestamp())}:R>",
        inline=True,
    )
    embed.add_field(
        name="Max Established Time",
        value=f"<t:{int((ts + timedelta(days=5)).timestamp())}:R>",
    )
    hubs: list[tuple[str, int]] = [
        ("HQ to Hek", 30002053),
        ("HQ to Dodixie", 30002659),
        ("HQ to Rens", 30002510),
        ("HQ to Jita", 30000142),
        ("HQ to Amarr", 30002187),
    ]
    for label, system_id in hubs:
        try:
            dist = route_length(solar_system.system_id, system_id)
            embed.add_field(name=label, value=str(dist), inline=True)
        except Exception:
            logger.debug("Route length failure %s", label, exc_info=True)
    embed.add_field(name="Gank Pipes In Route", value="Not Implemented")
    try:
        focus_inc = Focus.get_solo().incursion
        if focus_inc and focus_inc.staging_solar_system:
            trig = route_check_triglavian(solar_system.system_id, focus_inc.staging_solar_system.system_id)
            eden = route_check_edencom(solar_system.system_id, focus_inc.staging_solar_system.system_id)
            embed.add_field(name="Triglavian In Route", value=str(trig), inline=True)
            embed.add_field(name="Edencom In Route", value=str(eden), inline=True)
    except Exception:
        logger.debug("Failed trig/edencom route checks", exc_info=True)
    return embed


def embed_mobilizing(incursion: Incursion) -> Embed:
    constellation, region, _ = _resolve_incursion_location(incursion)
    embed = embed_base()
    embed.colour = Colour.yellow()
    embed.title = f"Mobilizing {incursion.security_string}: {region.name}/{constellation.name}"
    mt = incursion.mobilizing_timestamp
    et = incursion.established_timestamp
    if mt and et:
        try:
            uptime = mt - et
            unused = timedelta(days=5) - uptime
            pct = uptime / timedelta(days=5) * 100
        except Exception:
            uptime, unused, pct = None, None, None
    else:
        uptime = unused = pct = None
    if mt is None:
        mt = datetime.now()
    embed.add_field(
        name="Estimated Withdrawing Time",
        value=f"<t:{int((mt + timedelta(days=2)).timestamp())}:R>",
        inline=False,
    )
    embed.add_field(
        name="Estimated Despawn Time",
        value=f"<t:{int((mt + timedelta(days=3)).timestamp())}:R>",
        inline=False,
    )
    if uptime and unused and pct is not None:
        embed.add_field(
            name="Max Spawn Stats",
            value=f"{uptime} Uptime\n{unused} Unused\n{pct:.2f}% total possible `Established` time used",
        )
    embed.add_field(
        name="Estimated Respawn Window Opens",
        value=f"<t:{int((mt + timedelta(days=3, hours=12)).timestamp())}:R>",
        inline=False,
    )
    embed.add_field(
        name="Estimated Respawn Window Closes",
        value=f"<t:{int((mt + timedelta(days=3, hours=36)).timestamp())}:R>",
        inline=False,
    )
    return embed


def embed_withdrawing(incursion: Incursion) -> Embed:
    constellation, region, _ = _resolve_incursion_location(incursion)
    embed = embed_base()
    embed.colour = Colour.red()
    embed.title = f"Withdrawing {incursion.security_string}: {region.name}/{constellation.name}"
    wt = incursion.withdrawing_timestamp or datetime.now()
    embed.add_field(
        name="Despawn Before",
        value=f"<t:{int((wt + timedelta(days=1)).timestamp())}:R>",
        inline=False,
    )
    embed.add_field(
        name="Estimated Respawn Window Opens",
        value=f"<t:{int((wt + timedelta(days=1, hours=12)).timestamp())}:R>",
        inline=False,
    )
    embed.add_field(
        name="Estimated Respawn Window Closes",
        value=f"<t:{int((wt + timedelta(days=1, hours=36)).timestamp())}:R>",
        inline=False,
    )
    return embed


def embed_ended(incursion: Incursion) -> Embed:
    constellation, region, _ = _resolve_incursion_location(incursion)
    embed = embed_base()
    embed.colour = Colour.red()
    embed.description = "The next spawn will occur in 12-36 hours."
    embed.title = f"Ended {incursion.security_string}: {region.name}/{constellation.name}"
    et = incursion.ended_timestamp or datetime.now()
    embed.add_field(
        name="Spawn Window Opens",
        value=f"<t:{int((et + timedelta(days=1)).timestamp())}:R>",
        inline=False,
    )
    embed.add_field(
        name="Spawn Window Closes",
        value=f"<t:{int((et + timedelta(days=1, hours=12)).timestamp())}:R>",
        inline=False,
    )
    try:
        if incursion.withdrawing_timestamp:
            withdrawing_uptime = et - incursion.withdrawing_timestamp
            unused = timedelta(days=1) - withdrawing_uptime
            pct = withdrawing_uptime / timedelta(days=1) * 100
            embed.add_field(
                name="Max Spawn Stats",
                value=(
                    f"{withdrawing_uptime} Withdrawing\n"
                    f"{unused} Unused\n"
                    f"{pct:.2f}% total possible `Withdrawing` time used"
                ),
            )
    except Exception:
        logger.debug("Failed withdrawing stats", exc_info=True)
    return embed


def embed_boss_spawned(incursion: Incursion) -> Embed:
    constellation, _, _ = _resolve_incursion_location(incursion)
    embed = embed_base()
    embed.colour = Colour.yellow()
    embed.description = "Boss Spawned"
    embed.title = f"Boss has Spawned for {incursion.security_string}: {constellation.name}"
    return embed


def embed_waitlist_state(waitlist: Waitlist, is_open: bool) -> Embed:
    embed = embed_base()
    embed.url = f"{get_site_url()}/incursions/waitlist/"
    state_txt = "Open" if is_open else "Closed"
    embed.colour = Colour.green() if is_open else Colour.red()
    embed.description = f"Waitlist {state_txt}"
    embed.title = f"Waitlist {state_txt}"
    return embed
