from __future__ import annotations

from dataclasses import dataclass
from datetime import date
from pathlib import Path
from typing import Any

from src.errors import UserError
from src.matching.io import iter_image_files


@dataclass(frozen=True)
class MatchInfo:
    deal_id: int | None
    confidence: float | None
    match_reason: str
    deal_title: str | None
    venue_name: str | None
    place: str | None
    region: str | None


def match_inbox_photos(inbox_dir: Path) -> dict[str, MatchInfo]:
    deps = _import_matching_deps()
    cfg = deps.load_pipedrive_config()
    match_cfg = deps.load_matching_config()
    exif_results = _read_exif_results(inbox_dir, deps.read_exif)
    if not exif_results:
        return {}

    from_d, to_d = _date_range(exif_results)
    deals = _load_deals(cfg, from_d, to_d, deps)
    deals_by_id = _index_deals_by_id(deals)
    return _match_results(exif_results, deals, deals_by_id, match_cfg, deps)


@dataclass(frozen=True)
class _Deps:
    load_matching_config: Any
    load_pipedrive_config: Any
    read_exif: Any
    match_photo: Any
    PipedriveClient: Any
    load_mapping_file: Any
    resolve_mapping: Any
    normalize_deals_in_range: Any


def _import_matching_deps() -> _Deps:
    try:
        from src.config.matching_config import load_matching_config
        from src.config.pipedrive_config import load_pipedrive_config
        from src.exif.exif import read_exif
        from src.matching.matching import match_photo
        from src.pipedrive.client import PipedriveClient
        from src.pipedrive.mapping import (
            load_mapping_file,
            normalize_deals_in_range,
            resolve_mapping,
        )
    except Exception as exc:
        raise UserError(f"Failed to import matching dependencies: {exc}") from exc

    return _Deps(
        load_matching_config=load_matching_config,
        load_pipedrive_config=load_pipedrive_config,
        read_exif=read_exif,
        match_photo=match_photo,
        PipedriveClient=PipedriveClient,
        load_mapping_file=load_mapping_file,
        resolve_mapping=resolve_mapping,
        normalize_deals_in_range=normalize_deals_in_range,
    )


def _read_exif_results(inbox_dir: Path, read_exif: Any) -> list[Any]:
    out: list[Any] = []
    for src in iter_image_files(inbox_dir):
        out.append(read_exif(src))
    return out


def _date_range(exif_results: list[Any]) -> tuple[date, date]:
    dates = [r.datetime_utc.date() for r in exif_results]
    return (min(dates), max(dates))


def _load_deals(cfg: Any, from_d: date, to_d: date, deps: _Deps) -> list[Any]:
    client = deps.PipedriveClient(api_token=cfg.api_token, base_url=cfg.base_url)
    deal_fields = client.list_deal_fields()
    mapping_data = deps.load_mapping_file(cfg.mapping_path)
    mapping = deps.resolve_mapping(mapping_data, deal_fields)
    return deps.normalize_deals_in_range(
        client.iter_deals(), mapping, from_date=from_d, to_date=to_d
    )


def _match_results(
    exif_results: list[Any],
    deals: list[Any],
    deals_by_id: dict[int, Any],
    match_cfg: Any,
    deps: _Deps,
) -> dict[str, MatchInfo]:
    out: dict[str, MatchInfo] = {}
    for exif in exif_results:
        try:
            result = deps.match_photo(exif, deals, match_cfg)
            deal = None if result.deal_id is None else deals_by_id.get(int(result.deal_id))
            out[str(exif.path)] = MatchInfo(
                deal_id=result.deal_id,
                confidence=result.confidence,
                match_reason=result.match_reason,
                deal_title=_deal_attr(deal, "title"),
                venue_name=_deal_attr(deal, "venue_name"),
                place=_deal_attr(deal, "place"),
                region=_deal_attr(deal, "region"),
            )
        except Exception as exc:
            out[str(exif.path)] = MatchInfo(
                deal_id=None,
                confidence=None,
                match_reason=f"error:{exc}",
                deal_title=None,
                venue_name=None,
                place=None,
                region=None,
            )
    return out


def _index_deals_by_id(deals: list[Any]) -> dict[int, Any]:
    out: dict[int, Any] = {}
    for d in deals:
        deal_id = getattr(d, "deal_id", None)
        if isinstance(deal_id, int):
            out[deal_id] = d
    return out


def _deal_attr(deal: Any | None, name: str) -> str | None:
    if deal is None:
        return None
    value = getattr(deal, name, None)
    if value is None:
        return None
    text = str(value).strip()
    return text if text else None
