"""Import utilities for historical incursion data from everef datasets."""

# Standard Library
import bz2
import json
import lzma
import os
from datetime import datetime, timedelta, timezone

# Django
from django.db import IntegrityError

# Alliance Auth (Core)
from allianceauth.eveonline.models import EveFactionInfo
from allianceauth.services.hooks import get_extension_logger

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

from incursions.models.incursion import Incursion, IncursionInfluence

logger = get_extension_logger(__name__)


EVEREF_STAGING_FOLDER = os.path.join(
    os.path.dirname(os.path.abspath(__file__)),
    'data.everef.net/incursions/history/')


def _parse_filename_timestamp(name: str) -> datetime | None:
    try:
        return datetime.strptime(
            name.removesuffix(".json.bz2").removeprefix("incursions-"),
            '%Y-%m-%d_%H-%M-%S').replace(tzinfo=timezone.utc)
    except Exception:
        return None


def _apply_state_update(i: Incursion, state: str, ts) -> None:
    if state == "mobilizing" and not i.mobilizing_timestamp:
        i.mobilizing_timestamp = ts
    elif state == "withdrawing" and not i.withdrawing_timestamp:
        i.withdrawing_timestamp = ts
    if state and i.state is not Incursion.States.ENDED:
        i.state = state
    if isinstance(i.has_boss, str) and i.has_boss == "true":
        i.has_boss = True  # type: ignore[assignment]
    i.save()


def import_staging_history(dir: str = EVEREF_STAGING_FOLDER) -> None:
    for entry in sorted(os.scandir(dir), key=lambda e: e.name):
        if entry.is_dir():
            import_staging_history(entry.path)
            continue
        if not (entry.name.startswith('incursions-') and entry.is_file()):
            continue
        with bz2.open(entry) as f:
            data = f.read()
        ts = _parse_filename_timestamp(entry.name)
        if ts is None:
            logger.warning("Skipping file with unparsable timestamp: %s", entry.name)
            continue
        logger.debug("Processing everef snapshot %s", ts)
        try:
            payload = json.loads(data)
        except Exception:
            logger.exception("Invalid JSON in %s", entry.name)
            continue
        for incursion in payload:
            state = incursion.get('state')
            constellation_id = incursion.get('constellation_id')
            try:
                constellation = Constellation.objects.get(constellation_id=constellation_id)
            except Constellation.DoesNotExist:
                logger.debug("Constellation %s missing for everef import", constellation_id)
                continue
            i: Incursion | None = None
            try:
                if state == "established":
                    i = (Incursion.objects.get(established_timestamp__date__gte=ts.date() - timedelta(days=5), established_timestamp__date__lte=ts.date() + timedelta(days=5), constellation=constellation))
                elif state == "mobilizing":
                    i = (Incursion.objects.get(mobilizing_timestamp__date__gte=ts.date() - timedelta(days=2), mobilizing_timestamp__date__lte=ts.date() + timedelta(days=2), constellation=constellation))
                elif state == "withdrawing":
                    i = (Incursion.objects.get(withdrawing_timestamp__date__gte=ts.date() - timedelta(days=1), withdrawing_timestamp__date__lte=ts.date(), constellation=constellation))
            except Incursion.DoesNotExist:
                pass
            except Incursion.MultipleObjectsReturned as e:  # rare noisy edge
                logger.exception(e)
                continue
            if not i:
                # Attempt to create or attach to existing established record
                if state == "established":
                    try:
                        i = Incursion.objects.create(
                            constellation=constellation,
                            faction=EveFactionInfo.objects.get_or_create(faction_id=incursion.get('faction_id'))[0],
                            has_boss=bool(str(incursion.get('has_boss')).lower() == 'true'),
                            staging_solar_system=SolarSystem.objects.get(
                                system_id=incursion.get('staging_solar_system_id')
                            ),
                            state=state,
                            type=incursion.get('type', 'Incursion'),
                            established_timestamp=ts,
                        )
                    except Exception:
                        logger.debug("Failed to create established incursion for %s", constellation_id, exc_info=True)
                        continue
                else:
                    # Upgrades to mobilizing / withdrawing from earlier established timeline
                    lookback_days = 7 if state == "mobilizing" else 8
                    try:
                        i = (Incursion.objects.get(established_timestamp__date__gte=ts.date() - timedelta(days=lookback_days), established_timestamp__date__lte=ts.date(), constellation=constellation))
                    except Incursion.DoesNotExist:
                        # As last attempt for withdrawing, consider mobilizing window
                        if state == "withdrawing":
                            try:
                                i = (Incursion.objects.get(mobilizing_timestamp__date__gte=ts.date() - timedelta(days=2), mobilizing_timestamp__date__lte=ts.date(), constellation=constellation))
                            except Incursion.DoesNotExist:
                                logger.debug("No candidate incursion to set %s for %s", state, constellation_id)
                                continue
                        else:
                            logger.debug("No established incursion to set %s for %s", state, constellation_id)
                            continue
            # Apply state upgrade
            _apply_state_update(i, state, ts)
        # Mark ended
        for ended in (Incursion.objects.exclude(state=Incursion.States.ENDED).filter(established_timestamp__lte=ts)):
            ended.state = Incursion.States.ENDED
            ended.ended_timestamp = ts
            ended.save()


def import_staging_backfill(dir: str = EVEREF_STAGING_FOLDER) -> None:
    """Import legacy backfill archive for historical incursions.

    Note: This is a best-effort import; duplicates are ignored.
    """
    path = os.path.join(EVEREF_STAGING_FOLDER, "backfills/eve-incursions-de-2023-10-12.json.xz")
    if not os.path.exists(path):
        logger.warning("Backfill file missing: %s", path)
        return
    with lzma.open(path) as f:
        try:
            payload = json.loads(f.read())
        except Exception:
            logger.exception("Failed reading backfill JSON")
            return
    for incursion in payload:
        spawn = incursion.get('spawn', {})
        constellation_info = spawn.get('constellation', {})
        try:
            eve_constellation = Constellation.objects.get(constellation_id=constellation_info.get('id'))
        except Constellation.DoesNotExist:
            logger.debug("Constellation %s missing in backfill", constellation_info)
            continue
        ended_at_raw = spawn.get('endedAt')
        if ended_at_raw is None:
            logger.debug("Incursion not gracefully ended %s", incursion)
        est_date = spawn.get('establishedAt')
        end_date = spawn.get('endedAt')
        if not (est_date and end_date):
            continue
        est_dt = datetime.strptime(str(est_date), "%Y-%m-%dT%H:%M:%S.%fZ").replace(tzinfo=timezone.utc)
        end_dt = datetime.strptime(str(end_date), "%Y-%m-%dT%H:%M:%S.%fZ").replace(tzinfo=timezone.utc)
        try:
            i = Incursion.objects.get(
                established_timestamp__date=est_dt.date(),
                ended_timestamp__date=end_dt.date(),
                constellation=eve_constellation,
            )
        except Incursion.DoesNotExist:
            try:
                i = Incursion.objects.create(
                    constellation=eve_constellation,
                    faction=EveFactionInfo.objects.get_or_create(faction_id=500019)[0],
                    staging_solar_system=SolarSystem.objects.get(
                        name='UNKNOWN'
                    ),  # fallback; original relied on static mapping
                    state=Incursion.States.ENDED,
                    established_timestamp=est_dt,
                    ended_timestamp=end_dt,
                )
            except Exception:
                logger.debug(
                    "Failed creating backfill incursion for constellation %s",
                    eve_constellation.pk,
                    exc_info=True,
                )
                continue
        # Mobilizing / Withdrawing supplemental timestamps
        try:
            state = incursion.get('state')
            event_date_raw = incursion.get('date')
            if state and event_date_raw:
                event_dt = (datetime.strptime(
                    str(event_date_raw), "%Y-%m-%dT%H:%M:%S.%fZ")
                    .replace(tzinfo=timezone.utc))
                if state.lower() == 'mobilizing' and not i.mobilizing_timestamp:
                    i.mobilizing_timestamp = event_dt
                    i.save(update_fields=["mobilizing_timestamp"])
                elif state.lower() == 'withdrawing' and not i.withdrawing_timestamp:
                    i.withdrawing_timestamp = event_dt
                    i.save(update_fields=["withdrawing_timestamp"])
        except Exception:
            logger.debug("Supplemental state timestamp failure for %s", i.pk, exc_info=True)
        # Influence logs
        for ilog in spawn.get("influenceLogs", []):
            try:
                IncursionInfluence.objects.create(
                    incursion=i,
                    influence=ilog.get('influence', 0.0),
                    timestamp=(datetime.strptime(
                        str(ilog.get("date")), "%Y-%m-%dT%H:%M:%S.%fZ")
                        .replace(tzinfo=timezone.utc)),
                )
            except IntegrityError:
                continue

#   {
#     "id": "1",
#     "date": "2015-02-06T18:13:45.000Z",
#     "state": "Established",
#     "spawn": {
#       "id": "12",
#       "establishedAt": "2015-02-06T18:13:45.000Z",
#       "endedAt": "2015-02-09T16:00:01.000Z",
#       "state": "Ended",
#       "constellation": {
#         "id": "20000011"
#       },
#       "influenceLogs": [
#         {
#           "id": "293702",
#           "date": "2021-10-30T09:00:00.000Z",
#           "influence": 0
#         },
#         {
#           "id": "293707",
#           "date": "2021-10-30T10:00:00.000Z",
#           "influence": 0
#         },
#         {
#           "id": "293712",
#           "date": "2021-10-30T11:00:00.000Z",
#           "influence": 0.048
#         },
#         {
#           "id": "293717",
#           "date": "2021-10-30T12:00:00.000Z",
#           "influence": 0.175333
#         },
#     }
#   },
