"""Analyzes the ``aliexpress_ds_freight_query`` response for one SKU."""
from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Optional

from ....utils.helpers import (
    as_list,
    first_present,
    parse_delivery_days,
    round2,
    safe_get,
    to_float,
    unwrap_response,
)


@dataclass
class FreightAnalysis:
    """Normalized shipping option chosen for a SKU."""

    shipping_company: Optional[str] = None
    shipping_cost: float = 0.0
    delivery_days_min: Optional[int] = None
    delivery_days_max: Optional[int] = None
    tracking: bool = False
    warehouse_country: Optional[str] = None
    vat_included: bool = False
    option_count: int = 0
    has_data: bool = False

    @property
    def delivery_days_avg(self) -> Optional[float]:
        if self.delivery_days_min is None or self.delivery_days_max is None:
            return None
        return (self.delivery_days_min + self.delivery_days_max) / 2

    def to_dict(self) -> dict:
        return {
            "shippingCompany": self.shipping_company,
            "shippingCost": round2(self.shipping_cost),
            "deliveryDaysMin": self.delivery_days_min,
            "deliveryDaysMax": self.delivery_days_max,
            "tracking": self.tracking,
            "warehouseCountry": self.warehouse_country,
            "vatIncluded": self.vat_included,
            "optionCount": self.option_count,
        }


class FreightAnalyzer:
    """Single responsibility: pick and normalize the best freight option.

    A freight response may contain several shipping services. Business rule:
    prefer tracked services, then the cheapest, then the fastest.
    """

    def analyze(self, freight_json: Optional[dict]) -> FreightAnalysis:
        # Real API responses arrive wrapped in aliexpress_ds_freight_query_response
        # -> result envelopes; error responses unwrap to a dict with no options.
        payload = unwrap_response(freight_json or {})
        options = self._extract_options(payload)
        if not options:
            return FreightAnalysis(has_data=False)

        best = min(options, key=self._option_sort_key)
        best["option_count"] = len(options)
        return FreightAnalysis(
            shipping_company=best.get("company"),
            shipping_cost=best.get("cost", 0.0),
            delivery_days_min=best.get("days_min"),
            delivery_days_max=best.get("days_max"),
            tracking=best.get("tracking", False),
            warehouse_country=best.get("warehouse"),
            vat_included=best.get("vat", False),
            option_count=len(options),
            has_data=True,
        )

    # ------------------------------------------------------------------ #
    @staticmethod
    def _option_sort_key(option: dict) -> tuple:
        """Tracked first (False sorts before True when negated), then cheap, then fast."""
        return (
            not option.get("tracking", False),
            option.get("cost", float("inf")),
            option.get("days_max") or 999,
        )

    def _extract_options(self, freight_json: dict) -> list[dict]:
        raw_options: list[Any] = []
        # Known container shapes returned by aliexpress_ds_freight_query.
        for path in (
            "delivery_options.delivery_option_d_t_o",
            "delivery_options",
            "aeop_freight_calculate_result_for_buyer_d_t_o_list",
            "freight_options",
        ):
            container = safe_get(freight_json, path)
            if container:
                raw_options = as_list(container)
                break
        # Some payloads wrap the list one level deeper.
        if len(raw_options) == 1 and isinstance(raw_options[0], dict) and not self._looks_like_option(raw_options[0]):
            raw_options = as_list(next(iter(raw_options[0].values()), []))
        if not raw_options and self._looks_like_option(freight_json):
            raw_options = [freight_json]

        options = []
        for raw in raw_options:
            if isinstance(raw, dict):
                options.append(self._normalize_option(raw))
        return options

    @staticmethod
    def _looks_like_option(data: dict) -> bool:
        keys = set(data.keys())
        # Note: "code" is deliberately excluded; API error payloads also carry
        # a "code" field and must not be mistaken for a shipping option.
        return bool(
            keys & {"service_name", "shipping_fee_format", "freight", "company",
                    "estimated_delivery_time", "delivery_date_desc",
                    "max_delivery_days", "shipping_fee_cent"}
        )

    def _normalize_option(self, raw: dict) -> dict:
        # Currency-formatted fields first ("SAR19.00", {"amount": "19.00"}).
        cost_raw = first_present(
            raw,
            ("freight.amount", "shipping_fee_format", "shipping_cost", "cost", "fee"),
        )
        if cost_raw is not None:
            cost = to_float(cost_raw)
        else:
            # Cent-style fields are divided by 100 only when they are true
            # integer cents; some responses put currency units ("19.00") here.
            cent_raw = first_present(raw, ("freight.cent", "shipping_fee_cent"))
            cost = to_float(cent_raw)
            if cent_raw is not None and "." not in str(cent_raw) and "," not in str(cent_raw):
                cost = cost / 100.0

        # Explicit day fields first; "delivery_date_desc" holds calendar dates
        # ("Jul 15 - 18"), which must never be mistaken for day counts.
        days_min: int | None = None
        days_max: int | None = None
        min_raw = first_present(raw, ("min_delivery_days", "delivery_day_min"))
        max_raw = first_present(raw, ("max_delivery_days", "delivery_day_max"))
        if min_raw is not None or max_raw is not None:
            days_min = int(to_float(min_raw, to_float(max_raw)))
            days_max = int(to_float(max_raw, to_float(min_raw)))
        if days_min is None:
            days_min, days_max = parse_delivery_days(
                first_present(
                    raw,
                    ("estimated_delivery_time", "delivery_days",
                     "estimated_delivery_days", "time"),
                )
            )

        tracking_raw = first_present(raw, ("tracking", "tracking_available",
                                           "is_tracked"))
        tracking = str(tracking_raw).lower() in ("true", "1", "yes")

        vat_raw = first_present(
            raw, ("vat_included", "include_vat", "tax_included", "ddpIncludeVATTax")
        )
        vat = str(vat_raw).lower() in ("true", "1", "yes")

        return {
            "company": first_present(
                raw, ("service_name", "company", "shipping_company", "code")
            ),
            "cost": cost,
            "days_min": days_min,
            "days_max": days_max,
            "tracking": tracking,
            "warehouse": first_present(
                raw, ("ship_from_country", "send_goods_country", "warehouse_country",
                      "ship_from")
            ),
            "vat": vat,
        }
