from __future__ import annotations

import contextlib
import io
import json
import os
import tempfile
import unittest
from pathlib import Path
from typing import Any, Dict, Iterable, List
from unittest.mock import patch

from src.cli import main

TEST_TOKEN = "test-token"


DEAL_FIELDS: List[Dict[str, Any]] = [
    {"key": "cf_event_date", "name": "Event date"},
    {"key": "cf_venue", "name": "Venue"},
    {"key": "cf_lat", "name": "Lat"},
    {"key": "cf_lon", "name": "Lon"},
    {"key": "cf_place", "name": "Place"},
    {"key": "cf_region", "name": "Region"},
]


def _write_mapping_file(path: Path) -> None:
    path.write_text(
        json.dumps(
            {
                "schema_version": 1,
                "fields": {
                    "event_date": {"field_key": "cf_event_date"},
                    "venue_name": {"field_key": "cf_venue"},
                    "lat": {"field_key": "cf_lat"},
                    "lon": {"field_key": "cf_lon"},
                    "place": {"field_key": "cf_place"},
                    "region": {"field_key": "cf_region"},
                },
            }
        ),
        encoding="utf-8",
    )


class _StubGeocoder:
    def __init__(self) -> None:
        self.calls: list[tuple[float, float]] = []

    def reverse_geocode(self, lat: float, lon: float) -> tuple[str, str]:
        self.calls.append((lat, lon))
        return ("Amsterdam", "Noord-Holland")


class TestEnrich(unittest.TestCase):
    def test_cli_enrich_uses_pipedrive_when_place_region_present(self) -> None:
        deals = [
            {
                "id": 1,
                "title": "Deal 1",
                "cf_event_date": "2026-01-15",
                "cf_venue": "Venue A",
                "cf_lat": "52.1",
                "cf_lon": "4.9",
                "cf_place": " Amsterdam ",
                "cf_region": "Noord-Holland",
            }
        ]

        with tempfile.TemporaryDirectory() as tmp:
            mapping_path = Path(tmp) / "mapping.json"
            _write_mapping_file(mapping_path)
            env = {"PIPE_DRIVE_API_TOKEN": TEST_TOKEN, "PIPEDRIVE_FIELD_MAPPING_PATH": str(mapping_path)}

            stdout = io.StringIO()
            with (
                patch.dict(os.environ, env, clear=True),
                patch("src.pipedrive.client.PipedriveClient.list_deal_fields", return_value=DEAL_FIELDS),
                patch("src.pipedrive.client.PipedriveClient.iter_deals", return_value=iter(deals)),
                patch("src.enrich.geocode.load_reverse_geocode_client", side_effect=AssertionError("should not geocode")),
                contextlib.redirect_stdout(stdout),
            ):
                code = main(["enrich", "--from", "2026-01-01", "--to", "2026-01-31", "--dry-run"])

        self.assertEqual(code, 0)
        lines = [json.loads(line) for line in stdout.getvalue().splitlines() if line.strip()]
        self.assertEqual(len(lines), 1)
        self.assertEqual(lines[0]["deal_id"], 1)
        self.assertEqual(lines[0]["place"], "Amsterdam")
        self.assertEqual(lines[0]["region"], "Noord-Holland")
        self.assertEqual(lines[0]["source"], "pipedrive")

    def test_cli_enrich_fails_closed_when_geocode_needed_without_key(self) -> None:
        deals = [
            {
                "id": 1,
                "title": "Deal 1",
                "cf_event_date": "2026-01-15",
                "cf_venue": "Venue A",
                "cf_lat": "52.1",
                "cf_lon": "4.9",
                "cf_place": "Amsterdam",
                "cf_region": "",
            }
        ]

        with tempfile.TemporaryDirectory() as tmp:
            mapping_path = Path(tmp) / "mapping.json"
            _write_mapping_file(mapping_path)
            env = {
                "PIPE_DRIVE_API_TOKEN": TEST_TOKEN,
                "PIPEDRIVE_FIELD_MAPPING_PATH": str(mapping_path),
                "REVERSE_GEOCODE_BASE_URL": "https://example.com/reverse",
            }

            stderr = io.StringIO()
            with (
                patch.dict(os.environ, env, clear=True),
                patch("src.pipedrive.client.PipedriveClient.list_deal_fields", return_value=DEAL_FIELDS),
                patch("src.pipedrive.client.PipedriveClient.iter_deals", return_value=iter(deals)),
                contextlib.redirect_stderr(stderr),
            ):
                code = main(["enrich", "--from", "2026-01-01", "--to", "2026-01-31", "--dry-run"])

        self.assertEqual(code, 2)
        self.assertIn("Missing reverse geocode API key", stderr.getvalue())

    def test_cli_enrich_fills_missing_place_region_via_geocode(self) -> None:
        deals: Iterable[Dict[str, Any]] = [
            {
                "id": 1,
                "title": "Deal 1",
                "cf_event_date": "2026-01-15",
                "cf_venue": "Venue A",
                "cf_lat": 52.1,
                "cf_lon": 4.9,
                "cf_place": "",
                "cf_region": None,
            }
        ]

        stub = _StubGeocoder()

        with tempfile.TemporaryDirectory() as tmp:
            mapping_path = Path(tmp) / "mapping.json"
            _write_mapping_file(mapping_path)
            env = {"PIPE_DRIVE_API_TOKEN": TEST_TOKEN, "PIPEDRIVE_FIELD_MAPPING_PATH": str(mapping_path)}

            stdout = io.StringIO()
            with (
                patch.dict(os.environ, env, clear=True),
                patch("src.pipedrive.client.PipedriveClient.list_deal_fields", return_value=DEAL_FIELDS),
                patch("src.pipedrive.client.PipedriveClient.iter_deals", return_value=iter(deals)),
                patch("src.enrich.geocode.load_reverse_geocode_client", return_value=stub),
                contextlib.redirect_stdout(stdout),
            ):
                code = main(["enrich", "--from", "2026-01-01", "--to", "2026-01-31", "--dry-run"])

        self.assertEqual(code, 0)
        self.assertEqual(len(stub.calls), 1)
        lines = [json.loads(line) for line in stdout.getvalue().splitlines() if line.strip()]
        self.assertEqual(lines[0]["place"], "Amsterdam")
        self.assertEqual(lines[0]["region"], "Noord-Holland")
        self.assertEqual(lines[0]["source"], "reverse_geocode")

