from __future__ import annotations

import unittest
from datetime import datetime, timezone
from pathlib import Path

from src.config.matching_config import MatchingConfig
from src.exif.exif import ExifResult
from src.matching.matching import match_photo
from src.pipedrive.mapping import NormalizedDeal


def _deal(deal_id: int, ymd: str, *, lat: float, lon: float) -> NormalizedDeal:
    return NormalizedDeal(
        deal_id=deal_id,
        title=f"Deal {deal_id}",
        event_date=datetime.fromisoformat(f"{ymd}T00:00:00+00:00").date(),
        venue_name="Venue",
        lat=lat,
        lon=lon,
    )


def _photo(path: str, ymd: str, *, lat: float | None, lon: float | None) -> ExifResult:
    dt = datetime.fromisoformat(f"{ymd}T12:00:00+00:00")
    gps_source = "exif" if (lat is not None and lon is not None) else "missing"
    return ExifResult(
        path=Path(path),
        datetime_utc=dt.astimezone(timezone.utc),
        datetime_source="exif",
        gps_lat=lat,
        gps_lon=lon,
        gps_source=gps_source,
    )


class TestMatching(unittest.TestCase):
    def test_perfect_match_has_confidence_1(self) -> None:
        cfg = MatchingConfig(date_window_days=3, max_distance_m=25_000)
        deals = [_deal(1, "2026-01-15", lat=52.0, lon=4.0)]
        photo = _photo("a.jpg", "2026-01-15", lat=52.0, lon=4.0)

        result = match_photo(photo, deals, cfg)

        self.assertEqual(result.match_reason, "matched")
        self.assertEqual(result.deal_id, 1)
        self.assertEqual(result.date_delta_days, 0)
        self.assertEqual(result.distance_m, 0.0)
        self.assertEqual(result.confidence, 1.0)

    def test_tie_breaks_on_distance(self) -> None:
        cfg = MatchingConfig(date_window_days=3, max_distance_m=25_000)
        deals = [
            _deal(1, "2026-01-15", lat=52.0, lon=4.0),
            _deal(2, "2026-01-15", lat=52.1, lon=4.0),
        ]
        photo = _photo("a.jpg", "2026-01-15", lat=52.0, lon=4.0)

        result = match_photo(photo, deals, cfg)

        self.assertEqual(result.deal_id, 1)
        self.assertEqual(result.tie_breaker_reason, "distance")

    def test_tie_breaks_on_date_delta(self) -> None:
        cfg = MatchingConfig(date_window_days=3, max_distance_m=25_000)
        deals = [
            _deal(1, "2026-01-14", lat=52.0, lon=4.0),
            _deal(2, "2026-01-17", lat=52.0, lon=4.0),
        ]
        photo = _photo("a.jpg", "2026-01-15", lat=52.0, lon=4.0)

        result = match_photo(photo, deals, cfg)

        self.assertEqual(result.deal_id, 1)
        self.assertEqual(result.tie_breaker_reason, "date_delta_days")

    def test_tie_breaks_on_deal_id(self) -> None:
        cfg = MatchingConfig(date_window_days=3, max_distance_m=25_000)
        deals = [
            _deal(10, "2026-01-15", lat=52.0, lon=4.0),
            _deal(5, "2026-01-15", lat=52.0, lon=4.0),
        ]
        photo = _photo("a.jpg", "2026-01-15", lat=52.0, lon=4.0)

        result = match_photo(photo, deals, cfg)

        self.assertEqual(result.deal_id, 5)
        self.assertEqual(result.tie_breaker_reason, "deal_id")

    def test_missing_gps_is_no_match(self) -> None:
        cfg = MatchingConfig(date_window_days=3, max_distance_m=25_000)
        deals = [_deal(1, "2026-01-15", lat=52.0, lon=4.0)]
        photo = _photo("a.jpg", "2026-01-15", lat=None, lon=None)

        result = match_photo(photo, deals, cfg)

        self.assertIsNone(result.deal_id)
        self.assertEqual(result.match_reason, "missing_gps")
