import logging
import requests
from typing import Dict, Tuple
from collections import Counter

from django.conf import settings

from allianceauth.groupmanagement.models import Group
from allianceauth.services.modules.discord.models import DiscordUser
from requests.exceptions import HTTPError

from eveuniverse.models import EveSolarSystem, EveRegion
from esi.clients import EsiClientProvider

from .models import (
    MarketCharacter,
    TrackedStructure,
    TrackedContract,
    ContractSnapshot,
    HAS_FITTINGS,
    Fitting,
)

logger = logging.getLogger(__name__)
ESI_BASE_URL = "https://esi.evetech.net/latest"
esi = EsiClientProvider()


def resolve_ping_target(ping_value: str) -> str:
    if not ping_value:
        return ""
    if ping_value in ("@here", "@everyone"):
        return ping_value

    if ping_value.startswith("@"):
        group_name = ping_value[1:]
        try:
            group = Group.objects.get(name=group_name)
        except Group.DoesNotExist:
            return f"@{group_name}"

        try:
            discord_group_info = DiscordUser.objects.group_to_role(group=group)
        except HTTPError:
            return f"@{group_name}"
        except Exception:
            return f"@{group_name}"

        if discord_group_info and "id" in discord_group_info:
            return f"<@&{discord_group_info['id']}>"
        return f"@{group_name}"

    return ""



def location_display(scope: str, location_id: int) -> str:
    """Ładna nazwa lokalizacji (Region albo zapisany TrackedStructure)."""
    if scope == "region":
        try:
            return EveRegion.objects.get(id=location_id).name
        except EveRegion.DoesNotExist:
            return str(location_id)
    else:
        try:
            return TrackedStructure.objects.get(structure_id=location_id).name
        except TrackedStructure.DoesNotExist:
            return str(location_id)


def resolve_ping_target_from_config(config) -> str:
    """
    Zwraca treść do 'content' dla Discorda na podstawie MarketTrackingConfig:
    - @here / @everyone / "" (none)
    - lub <@&ROLE_ID> dla zmapowanej grupy
    """
    if config.discord_ping_group:
        try:
            mapping = DiscordUser.objects.group_to_role(group=config.discord_ping_group)
            role_id = mapping.get("id") if mapping else None
            if role_id:
                return f"<@&{role_id}>"
        except HTTPError:
            logger.exception("[MarketTracker] Discord service error when resolving group role")

        return f"@{config.discord_ping_group.name}"

    v = (config.discord_ping_group_text or "").strip()
    if v in {"here", "@here"}:
        return "@here"
    if v in {"everyone", "@everyone"}:
        return "@everyone"
    return ""


_ALLOWED_SLOT_PREFIXES = (
    "LoSlot", "MedSlot", "HiSlot",
    "RigSlot", "SubSystemSlot", "ServiceSlot",
)
_IGNORE_FLAGS = {"Cargo", "DroneBay", "FighterBay", "Invalid"}


def _fitting_requirements(fitting: Fitting) -> Tuple[int, Dict[int, int]]:
    hull_id = int(fitting.ship_type_id)
    req = Counter()
    for it in fitting.items.all():
        flag = it.flag or ""
        if flag in _IGNORE_FLAGS:
            continue
        if not flag.startswith(_ALLOWED_SLOT_PREFIXES):
            continue
        req[int(it.type_id)] += int(it.quantity or 1)
    return hull_id, dict(req)


def _contract_items_as_counter(contract) -> Tuple[int | None, Dict[int, int]]:
    items = getattr(contract, "items", None) or []
    counts = Counter()
    hull_id = None
    for it in items:
        try:
            t = int(it.get("type_id"))
            q = int(it.get("quantity") or it.get("quantity_delivered") or 0)
            counts[t] += q
            if it.get("is_singleton"):
                hull_id = hull_id or t
        except Exception:
            continue
    return hull_id, dict(counts)


def contract_matches(tc: TrackedContract, snap: ContractSnapshot):
    """
    Sprawdza, czy ContractSnapshot (snap) pasuje do TrackedContract (tc).

    Zwraca:
        (ok: bool, reason: str | None)

    reason może być m.in.:
        "type", "status", "price", "title", "fit", "items"
    """

    # 0) wyłączone trackowanie
    if not tc.is_active:
        return False, None

    # 1) filtr typu i statusu
    if (snap.type or "").lower() != "item_exchange":
        return False, "type"

    if (snap.status or "").lower() != "outstanding":
        return False, "status"

    # 2) filtr ceny maksymalnej
    if tc.max_price and float(tc.max_price) > 0:
        price = float(snap.price or 0)
        if price > float(tc.max_price):
            logger.debug(
                "[match] snap %s price %.2f > max %.2f",
                snap.contract_id,
                price,
                float(tc.max_price),
            )
            return False, "price"

    # 3) dane wspólne – tytuł kontraktu
    title = (snap.title or "").strip()

    # 4) tryb CUSTOM – szukamy fragmentu tekstu w tytule
    if tc.mode == TrackedContract.Mode.CUSTOM:
        filt = (tc.title_filter or "").strip()
        if not filt:
            # nic nie szukamy -> nic nie pasuje
            return False, "title"

        if filt.lower() not in title.lower():
            logger.debug(
                "[match] snap %s title '%s' !contains '%s'",
                snap.contract_id,
                title,
                filt,
            )
            return False, "title"

        return True, None

    # 5) tryb DOCTRINE_FIT – porównanie itemów
    if tc.mode == TrackedContract.Mode.DOCTRINE:
        fit = tc.fitting
        if not fit or not getattr(fit, "ship_type_id", None):
            return False, "fit"

        # kontrakt musi zawierać shipa z fita
        ship_type_id = int(fit.ship_type_id)

        # items z ESI trzymane w ContractSnapshot.items
        items = snap.items or []
        if not items:
            logger.debug("[match] snap %s has no items json", snap.contract_id)
            return False, "items"

        # policz ilości type_id w kontrakcie
        contract_counts: dict[int, int] = {}
        for it in items:
            try:
                t_id = int(it.get("type_id"))
                qty = int(it.get("quantity") or 0)
            except (TypeError, ValueError):
                continue
            contract_counts[t_id] = contract_counts.get(t_id, 0) + qty

        # 5a) czy jest właściwy ship?
        if contract_counts.get(ship_type_id, 0) < 1:
            logger.debug(
                "[match] snap %s missing ship type_id=%s",
                snap.contract_id,
                ship_type_id,
            )
            return False, "fit"

        # 5b) wymagane moduły z fita (tak jak wcześniej)
        required_items: dict[int, int] = {}

        for slot in ("high_slots", "mid_slots", "low_slots", "rigs", "subsystems"):
            for mod in getattr(fit, slot, []) or []:
                try:
                    t_id = int(mod.type_id)
                except (TypeError, ValueError):
                    continue
                required_items[t_id] = required_items.get(t_id, 0) + 1

        # sprawdzamy, czy kontrakt ma >= tyle samo każdego modułu
        for t_id, req_qty in required_items.items():
            have_qty = contract_counts.get(t_id, 0)
            if have_qty < req_qty:
                logger.debug(
                    "[match] snap %s missing module type_id=%s (have %s, need %s)",
                    snap.contract_id,
                    t_id,
                    have_qty,
                    req_qty,
                )
                return False, "fit"

        # wszystko gra
        return True, None

    # jeśli kiedykolwiek pojawi się inny mode – na wszelki wypadek nic nie matchujemy
    return False, "mode"




def fetch_contract_items(contract_obj, access_token, char_id):
    """
    Pobiera items kontraktu TYLKO gdy są potrzebne.
    Zapisuje je w ContractSnapshot.items i zwraca.
    """
    if contract_obj.items:
        return contract_obj.items

    url = f"{ESI_BASE_URL}/characters/{char_id}/contracts/{contract_obj.contract_id}/items/"
    headers = {
        "User-Agent": getattr(settings, "ESI_USER_AGENT", "MarketTracker/1.0"),
        "Authorization": f"Bearer {access_token}",
    }

    try:
        resp = requests.get(url, headers=headers, timeout=10)
        resp.raise_for_status()
        items = resp.json()
        contract_obj.items = items
        contract_obj.save(update_fields=["items"])
        return items
    except Exception as e:
        logger.error("[Contracts] Failed to load items for contract %s: %s", contract_obj.contract_id, e)
        return []
