from dataclasses import dataclass
from datetime import datetime
from itertools import groupby
from zoneinfo import ZoneInfo

from bb_integrations_lib.models.sd.bols_and_drops import Freight
from bb_integrations_lib.util.utils import lookup


@dataclass
class Entity:
    use_source_id: bool
    source_id_key: str
    fallback_field: str = "name"


def safe_get_extra_data(obj: dict, key: str):
    """Get a value from an object's extra_data dict."""
    if "extra_data" in obj:
        return obj.get("extra_data", {}).get(key)
    return None


def to_timezone(dt: datetime, zone: str) -> datetime | None:
    """Convert a datetime to the given timezone from UTC."""
    if not dt:
        return None
    if not dt.tzinfo:
        dt = dt.replace(tzinfo=ZoneInfo("UTC"))
    try:
        return dt.astimezone(ZoneInfo(zone)).replace(tzinfo=None)
    except OverflowError:
        return dt.replace(tzinfo=None)


def get_entity_id(entities: dict[str, Entity], entity_type: str, entity_data: dict) -> str | None:
    """Get the identifier for an entity based on the entity config."""
    entity_config = entities.get(entity_type)
    if not entity_config:
        return None
    if entity_config.use_source_id:
        return safe_get_extra_data(entity_data, entity_config.source_id_key)
    return entity_data.get(entity_config.fallback_field)


def aggregate_accessorials(freight: Freight | None, agg_key: str = "total", agg_group: str = "subtype") -> dict:
    """Aggregate accessorial FreightTransactions by a grouping key, summing the agg_key values."""
    if not freight:
        return {}
    accessorials = [t for t in freight.transactions if t.type == "Accessorial"]
    sorted_details = sorted(accessorials, key=lambda x: getattr(x, agg_group, None) or "")
    aggregates = {}
    for key, group in groupby(sorted_details, key=lambda x: getattr(x, agg_group, None)):
        group_list = list(group)
        total = sum(getattr(detail, agg_key, None) or 0 for detail in group_list)
        label = (key or "").strip()
        aggregates[label] = {"total": total}
    return aggregates


def build_accessorials_strings(freight: Freight | None, delimiter: str = ",") -> tuple[str, str]:
    """
    Aggregate accessorial transactions from a Freight model
    and return joined subtypes and amounts strings.
    """
    aggregated = aggregate_accessorials(freight)
    subtypes = list(aggregated.keys())
    amounts = [str(v.get("total")) for v in aggregated.values()]
    subtypes_str = delimiter.join(subtypes) if subtypes else ""
    amounts_str = delimiter.join(amounts) if amounts else ""
    return subtypes_str, amounts_str


def build_lookups(
    counterparties: list[dict],
    locations: list[dict],
    stores: list[dict],
    products: list[dict],
    drivers: list[dict],
) -> dict:
    """Build the standard set of entity lookups used by order parsers."""
    cp_lkp = lookup(counterparties, lambda x: x.get("id"))
    return {
        "cp_lkp": cp_lkp,
        "loc_lkp": lookup(locations, lambda x: x.get("id")),
        "store_lkp": lookup(stores, lambda x: x.get("_id")),
        "prod_lkp": lookup(products, lambda x: x.get("id")),
        "driver_lkp": lookup(drivers, lambda x: x.get("id")),
        "store_lkp_by_number": lookup(stores, lambda x: x.get("store_number")),
        "internal_cp_names": [
            c.get("name") for _id, c in cp_lkp.items() if "Internal" in (c.get("types") or [])
        ],
    }
