# -*- coding: utf-8 -*-
"""Справочник ISO-контейнеров: промпт RAG и сверка п.21 FCL (дверной проём vs габариты груза)."""
from __future__ import annotations

import json
import os
import re
from typing import Any, Dict, List, Optional, Tuple

_DIR = os.path.dirname(os.path.abspath(__file__))
_JSON_PATH = os.path.join(_DIR, "container_iso_reference.json")

_iso_cache: Optional[Dict[str, Any]] = None


def load_iso_reference() -> Dict[str, Any]:
    global _iso_cache
    if _iso_cache is not None:
        return _iso_cache
    try:
        with open(_JSON_PATH, "r", encoding="utf-8") as f:
            _iso_cache = json.load(f)
    except Exception:
        _iso_cache = {"types": {}}
    return _iso_cache


def iso_reference_prompt_block() -> str:
    """Компактный блок для system prompt LLM (сверка п.21 и выбора контейнера)."""
    data = load_iso_reference()
    types = data.get("types") or {}
    if not types:
        return ""
    lines: List[str] = [
        "СПРАВОЧНИК ISO-КОНТЕЙНЕРОВ (мм, кг, м³) — для сверки с п.21 FCL (дверной проём) и оценки объёма/полезной нагрузки. "
        "Не выдумывай числа; при расчётах используй только эти значения.",
        "Ключи 20DV / 40DV / 40HC соответствуют формулировкам в критериях (проём 20DV, 40DV, 40HC).",
    ]
    for key in ("20DV", "20HC", "20HC_PW", "40DV", "40HC", "40DC", "40HC_PW", "45HC_PW_A", "45HC_PW_B"):
        t = types.get(key)
        if not isinstance(t, dict):
            continue
        door = t.get("door_opening_mm") or {}
        inn = t.get("internal_mm") or {}
        dw = door.get("width")
        dh = door.get("height")
        il = inn.get("length")
        iw = inn.get("width")
        ih = inn.get("height")
        vol = t.get("volume_m3")
        pay = t.get("payload_kg")
        lines.append(
            f"- {key} ({t.get('label', '')}): дверь {dw}×{dh} мм; "
            f"внутри {il}×{iw}×{ih} мм; объём ~{vol} м³; payload ~{pay} кг"
        )
    return "\n".join(lines)


def _edges_mm_from_dim(d: Dict[str, Any]) -> Optional[Tuple[float, float, float]]:
    """Длина/ширина/высота груза в мм (из см в shipment)."""
    if not isinstance(d, dict):
        return None

    def _num(v: Any) -> Optional[float]:
        if v is None:
            return None
        if isinstance(v, (int, float)):
            return float(v)
        s = str(v).strip().replace(" ", "").replace(",", ".")
        try:
            return float(s)
        except ValueError:
            return None

    l = _num(d.get("length_cm"))
    w = _num(d.get("width_cm"))
    h = _num(d.get("height_cm"))
    if l is None or w is None or h is None:
        return None
    return l * 10.0, w * 10.0, h * 10.0


def _box_fits_door(
    edges_mm: Tuple[float, float, float],
    door_w_mm: float,
    door_h_mm: float,
) -> bool:
    """Два наименьших ребра груза должны укладываться в ширину/высоту проёма (сортировка)."""
    s0, s1, s2 = sorted(edges_mm)
    dw, dh = sorted((door_w_mm, door_h_mm))
    return s0 <= dw + 0.5 and s1 <= dh + 0.5


def _codes_from_container_type(ct: str) -> List[str]:
    """Из строки вида «2×40HC, 1×20DC» извлекает коды из справочника."""
    if not isinstance(ct, str) or not ct.strip():
        return ["20DV", "40DV", "40HC"]
    t = ct.upper()
    found: List[str] = []

    def _add(size: str, kind: Optional[str]) -> None:
        k = (kind or "DV").upper()
        if size == "20":
            code = "20" + ("HC" if k in ("HC", "HQ") else "DV")
        elif size == "40":
            code = "40" + ("HC" if k in ("HC", "HQ") else "DV")
        elif size == "45":
            code = "45HC_PW_B"
        else:
            return
        if code not in found:
            found.append(code)

    for m in re.finditer(
        r"(\d{1,3})\s*[x×х*＊]\s*(20|40|45)\s*(DC|DV|GP|HC|HQ|OT|FR|RF|RH)?",
        t,
        re.IGNORECASE,
    ):
        _add(m.group(2), m.group(3))
    for m in re.finditer(
        r"\b(20|40|45)\s*(DC|DV|GP|HC|HQ|OT|FR|RF|RH)\b",
        t,
        re.IGNORECASE,
    ):
        _add(m.group(1), m.group(2))
    if not found:
        return ["20DV", "40DV", "40HC"]
    return found


def door_clearance_summary(shipment: Dict[str, Any]) -> str:
    """
    Текст для п.21: сверка габаритов мест с дверным проёмом 20DV/40DV/40HC из справочника.
    """
    data = load_iso_reference()
    types = data.get("types") or {}
    dims = shipment.get("dimensions")
    if not isinstance(dims, list) or not dims:
        return (
            "Нет габаритов грузовых мест — проверка проходимости по справочнику дверных проёмов "
            "(20DV 2336×2291 мм, 40DV 2336×2291 мм, 40HC 2340×2597 мм) невозможна."
        )

    ct_raw = (shipment.get("container_type") or "").strip()
    codes = _codes_from_container_type(ct_raw)
    parts: List[str] = []
    for i, d in enumerate(dims, start=1):
        edges = _edges_mm_from_dim(d)
        if not edges:
            parts.append(f"Место {i}: нет полных Д×Ш×В в см.")
            continue
        place_bits: List[str] = []
        for code in codes:
            spec = types.get(code)
            if not isinstance(spec, dict):
                continue
            door = spec.get("door_opening_mm") or {}
            dw = float(door.get("width") or 0)
            dh = float(door.get("height") or 0)
            if dw <= 0 or dh <= 0:
                continue
            ok = _box_fits_door(edges, dw, dh)
            label = spec.get("label", code)
            status = "проходит" if ok else "не проходит"
            place_bits.append(f"{code} ({int(dw)}×{int(dh)} мм): {status}")
        if place_bits:
            parts.append(f"Место {i} (рёбра {int(min(edges))}×{int(sorted(edges)[1])}×{int(max(edges))} мм): " + "; ".join(place_bits))
        else:
            parts.append(f"Место {i}: нет данных дверного проёма в справочнике для кодов {', '.join(codes)}.")

    note = (
        " Оценка: два наименьших габарита сравниваются с шириной и высотой дверного проёма (ориентация коробки)."
    )
    prefix = (
        f"Справочник дверных проёмов (п.21). Контейнеры в заявке: {ct_raw or 'не указаны — проверка по 20DV/40DV/40HC'}. "
    )
    return prefix + " ".join(parts) + note

