"""Celery tasks for PI FYAD — ESI sync via Celery Beat."""

import logging

from celery import shared_task
from django.utils import timezone

from .constants import PI_SCOPES

logger = logging.getLogger(__name__)


@shared_task(bind=True, max_retries=3, default_retry_delay=60)
def sync_all_planets(self):
    """Master task: sync planet data for all tracked characters."""
    from .models import CharacterOwner

    owners = CharacterOwner.objects.filter(sync_enabled=True)
    synced = 0
    errors = 0

    for owner in owners:
        if owner.needs_planet_sync:
            try:
                sync_character_planets(owner.character.character_id)
                synced += 1
            except Exception as e:
                logger.error(
                    "Failed to sync planets for %s: %s",
                    owner.character.character_name,
                    e,
                )
                errors += 1

    logger.info(
        "Planet sync complete: %d synced, %d errors out of %d owners",
        synced,
        errors,
        owners.count(),
    )
    return {"synced": synced, "errors": errors}


@shared_task(bind=True, max_retries=3, default_retry_delay=60)
def sync_character_planets(self, character_id: int):
    """Sync all planet data for a single character via ESI."""
    from allianceauth.eveonline.models import EveCharacter
    from esi.clients import EsiClientProvider

    from .models import CharacterOwner, CharacterPlanet, PlanetPin, StorageContents

    esi = EsiClientProvider()

    try:
        character = EveCharacter.objects.get(character_id=character_id)
    except EveCharacter.DoesNotExist:
        logger.error("Character %d not found", character_id)
        return

    owner, _ = CharacterOwner.objects.get_or_create(character=character)
    token = _get_token(character, PI_SCOPES)
    if not token:
        logger.warning("No valid token for %s", character.character_name)
        return

    # Fetch planet list
    try:
        planets_data = esi.client.Planetary.get_characters_character_id_planets(
            character_id=character_id,
            token=token.valid_access_token(),
        ).results()
    except Exception as e:
        logger.error("ESI planet list failed for %s: %s", character.character_name, e)
        raise self.retry(exc=e)

    current_planet_ids = set()

    for planet_info in planets_data:
        planet_id = planet_info["planet_id"]
        current_planet_ids.add(planet_id)

        planet, _ = CharacterPlanet.objects.update_or_create(
            owner=owner,
            planet_id=planet_id,
            defaults={
                "planet_type_id": planet_info.get("planet_type_id", 0),
                "solar_system_id": planet_info.get("solar_system_id", 0),
                "upgrade_level": planet_info.get("upgrade_level", 0),
                "num_pins": planet_info.get("num_pins", 0),
                "last_update": timezone.now(),
            },
        )

        # Fetch pin details for this planet
        try:
            pins_data = esi.client.Planetary.get_characters_character_id_planets_planet_id(
                character_id=character_id,
                planet_id=planet_id,
                token=token.valid_access_token(),
            ).results()
        except Exception as e:
            logger.error(
                "ESI pin detail failed for %s planet %d: %s",
                character.character_name,
                planet_id,
                e,
            )
            continue

        _process_pins(planet, pins_data.get("pins", []))

    # Remove planets no longer in ESI response
    CharacterPlanet.objects.filter(owner=owner).exclude(
        planet_id__in=current_planet_ids
    ).delete()

    owner.last_planet_sync = timezone.now()
    owner.save(update_fields=["last_planet_sync"])

    logger.info("Synced %d planets for %s", len(current_planet_ids), character.character_name)


@shared_task(bind=True, max_retries=3, default_retry_delay=60)
def sync_customs_offices(self, character_id: int):
    """Sync customs office data for a character's planets."""
    from allianceauth.eveonline.models import EveCharacter
    from esi.clients import EsiClientProvider

    from .models import CharacterOwner, CustomsOffice

    esi = EsiClientProvider()

    try:
        character = EveCharacter.objects.get(character_id=character_id)
    except EveCharacter.DoesNotExist:
        logger.error("Character %d not found", character_id)
        return

    owner = CharacterOwner.objects.filter(character=character).first()
    if not owner:
        return

    token = _get_token(character, PI_SCOPES)
    if not token:
        logger.warning("No valid token for %s", character.character_name)
        return

    try:
        offices_data = esi.client.Planetary.get_characters_character_id_planets(
            character_id=character_id,
            token=token.valid_access_token(),
        ).results()
    except Exception as e:
        logger.error("ESI customs offices failed for %s: %s", character.character_name, e)
        raise self.retry(exc=e)

    for planet in owner.planets.all():
        for office in offices_data:
            if office.get("planet_id") == planet.planet_id:
                CustomsOffice.objects.update_or_create(
                    planet=planet,
                    defaults={
                        "office_id": office.get("office_id", 0),
                        "owner_id": office.get("owner_id"),
                        "tax_rate": office.get("tax_rate", 0.0),
                        "reinforcement_exit_start": office.get("reinforce_exit_start", 0),
                        "reinforcement_exit_end": office.get("reinforce_exit_end", 0),
                        "allow_alliance": office.get("allow_alliance_access", False),
                        "allow_standings": office.get("allow_access_with_standings", False),
                        "last_update": timezone.now(),
                    },
                )

    owner.last_customs_sync = timezone.now()
    owner.save(update_fields=["last_customs_sync"])
    logger.info("Synced customs offices for %s", character.character_name)


@shared_task
def cleanup_stale_data(days: int = 30):
    """Remove PI data for characters that haven't synced in N days."""
    from .models import CharacterOwner

    cutoff = timezone.now() - timezone.timedelta(days=days)
    stale = CharacterOwner.objects.filter(
        last_planet_sync__lt=cutoff,
        sync_enabled=False,
    )
    count = stale.count()
    stale.delete()
    logger.info("Cleaned up %d stale character owners (older than %d days)", count, days)
    return {"deleted": count}


def _process_pins(planet, pins_data: list):
    """Process ESI pin data and update/create PlanetPin records."""
    from .constants import (
        PIN_GROUP_EXTRACTOR,
        PIN_GROUP_PROCESSOR_ADVANCED,
        PIN_GROUP_PROCESSOR_BASIC,
        PIN_GROUP_PROCESSOR_HIGHTECH,
        PIN_GROUP_STORAGE,
        PIN_GROUP_LAUNCHPAD,
        SCHEMATIC_TIER_MAP,
        STORAGE_CAPACITIES,
    )
    from .models import PlanetPin, StorageContents

    current_pin_ids = set()

    for pin_data in pins_data:
        pin_id = pin_data["pin_id"]
        type_id = pin_data.get("type_id", 0)
        current_pin_ids.add(pin_id)

        # Determine pin classification
        group_id = pin_data.get("group_id", 0)
        is_extractor = group_id == PIN_GROUP_EXTRACTOR
        is_factory = group_id in (
            PIN_GROUP_PROCESSOR_BASIC,
            PIN_GROUP_PROCESSOR_ADVANCED,
            PIN_GROUP_PROCESSOR_HIGHTECH,
        )
        is_storage = group_id in (PIN_GROUP_STORAGE, PIN_GROUP_LAUNCHPAD)

        # Factory tier
        factory_tier = 0
        if group_id == PIN_GROUP_PROCESSOR_BASIC:
            factory_tier = 1
        elif group_id == PIN_GROUP_PROCESSOR_ADVANCED:
            factory_tier = 2
        elif group_id == PIN_GROUP_PROCESSOR_HIGHTECH:
            factory_tier = 3

        # Extractor fields
        extractor_details = pin_data.get("extractor_details", {}) or {}
        heads = extractor_details.get("heads", [])

        pin_defaults = {
            "type_id": type_id,
            "type_name": pin_data.get("type_name", ""),
            "group_id": group_id,
            "schematic_id": pin_data.get("schematic_id"),
            "latitude": pin_data.get("latitude", 0.0),
            "longitude": pin_data.get("longitude", 0.0),
            "is_extractor": is_extractor,
            "is_factory": is_factory,
            "is_storage": is_storage,
            "factory_tier": factory_tier,
            "factory_output_type_id": pin_data.get("factory_details", {}).get("output", {}).get("type_id") if pin_data.get("factory_details") else None,
            "factory_output_name": "",
            "extractor_product_type_id": extractor_details.get("product_type_id"),
            "extractor_cycle_time": extractor_details.get("cycle_time"),
            "extractor_head_count": len(heads),
            "extractor_qty_per_cycle": extractor_details.get("qty_per_cycle", 0),
            "install_time": pin_data.get("install_time"),
            "expiry_time": pin_data.get("expiry_time"),
            "last_update": timezone.now(),
        }

        # Storage capacity
        if is_storage:
            pin_defaults["storage_capacity_m3"] = STORAGE_CAPACITIES.get(type_id, 0.0)

        pin, _ = PlanetPin.objects.update_or_create(
            planet=planet,
            pin_id=pin_id,
            defaults=pin_defaults,
        )

        # Update storage contents
        if is_storage:
            contents = pin_data.get("contents", []) or []
            StorageContents.objects.filter(pin=pin).delete()
            total_used = 0.0
            for item in contents:
                vol_per_unit = item.get("volume_per_unit", 0.01)
                qty = item.get("amount", 0)
                total_vol = vol_per_unit * qty
                total_used += total_vol
                StorageContents.objects.create(
                    pin=pin,
                    type_id=item.get("type_id", 0),
                    type_name=item.get("type_name", ""),
                    quantity=qty,
                    volume_per_unit=vol_per_unit,
                    total_volume=total_vol,
                )
            pin.storage_used_m3 = total_used
            pin.update_storage_capacity()
            pin.save(update_fields=["storage_used_m3", "storage_capacity_m3", "storage_fill_pct"])

    # Remove pins no longer present
    PlanetPin.objects.filter(planet=planet).exclude(pin_id__in=current_pin_ids).delete()


def _get_token(character, scopes):
    """Get a valid ESI token for a character with required scopes."""
    from esi.models import Token

    try:
        return Token.objects.filter(
            character_id=character.character_id,
        ).require_scopes(scopes).first()
    except Exception:
        return None
