"""Tests for yoro-maps core — database, routing, countries."""

import sqlite3
import tempfile
from pathlib import Path

import pytest

from yoromaps.countries import COUNTRIES, geofabrik_url
from yoromaps.db import open_db, set_metadata, get_metadata, SCHEMA_SQL
from yoromaps.extract import haversine
from yoromaps.routing import route, RouteResult, _nearest_node


# ---------- Countries ----------

class TestCountries:
    def test_mali_exists(self):
        assert "ML" in COUNTRIES
        assert COUNTRIES["ML"].name == "Mali"

    def test_burkina_exists(self):
        assert "BF" in COUNTRIES

    def test_niger_exists(self):
        assert "NE" in COUNTRIES

    def test_geofabrik_url(self):
        url = geofabrik_url("ML")
        assert "mali" in url
        assert url.endswith(".osm.pbf")

    def test_unknown_country_raises(self):
        with pytest.raises(ValueError):
            geofabrik_url("ZZ")

    def test_all_countries_have_bbox(self):
        for code, c in COUNTRIES.items():
            assert len(c.bbox) == 4, f"{code} missing bbox"


# ---------- Database ----------

class TestDatabase:
    def test_create_db(self):
        with tempfile.TemporaryDirectory() as d:
            path = Path(d) / "test.yoromaps"
            conn = open_db(path, create=True)
            # Check tables exist
            tables = [r[0] for r in conn.execute(
                "SELECT name FROM sqlite_master WHERE type='table'"
            ).fetchall()]
            assert "nodes" in tables
            assert "edges" in tables
            assert "tiles" in tables
            assert "pois" in tables
            assert "metadata" in tables
            conn.close()

    def test_metadata(self):
        with tempfile.TemporaryDirectory() as d:
            path = Path(d) / "test.yoromaps"
            conn = open_db(path, create=True)
            set_metadata(conn, "country", "ML")
            assert get_metadata(conn, "country") == "ML"
            assert get_metadata(conn, "nonexistent") is None
            conn.close()

    def test_open_nonexistent_raises(self):
        with pytest.raises(FileNotFoundError):
            open_db("/tmp/nonexistent_yoromaps_test.db")


# ---------- Haversine ----------

class TestHaversine:
    def test_same_point(self):
        assert haversine(12.0, -8.0, 12.0, -8.0) == 0.0

    def test_bamako_to_mopti(self):
        # ~460 km straight line
        d = haversine(12.639, -8.003, 14.489, -4.197)
        assert 440_000 < d < 480_000

    def test_symmetric(self):
        d1 = haversine(12.0, -8.0, 14.0, -4.0)
        d2 = haversine(14.0, -4.0, 12.0, -8.0)
        assert abs(d1 - d2) < 0.01


# ---------- Routing ----------

class TestRouting:
    @pytest.fixture
    def graph_db(self):
        """Create an in-memory graph for testing."""
        conn = sqlite3.connect(":memory:")
        conn.row_factory = sqlite3.Row
        conn.executescript(SCHEMA_SQL)

        # Simple graph: A -- B -- C
        #                      |
        #                      D
        nodes = [
            (1, 12.0, -8.0),   # A (Bamako-ish)
            (2, 12.5, -7.0),   # B
            (3, 13.0, -6.0),   # C
            (4, 11.5, -7.0),   # D (south of B)
        ]
        conn.executemany("INSERT INTO nodes VALUES (?,?,?)", nodes)

        edges = [
            (1, 2, 120000, 5400, "primary", 0, "Route de Segou"),  # A→B
            (2, 1, 120000, 5400, "primary", 0, "Route de Segou"),  # B→A
            (2, 3, 130000, 5850, "primary", 0, "Route de Mopti"),  # B→C
            (3, 2, 130000, 5850, "primary", 0, "Route de Mopti"),  # C→B
            (2, 4, 115000, 5175, "secondary", 0, "Route du Sud"),  # B→D
            (4, 2, 115000, 5175, "secondary", 0, "Route du Sud"),  # D→B
        ]
        conn.executemany(
            "INSERT INTO edges (from_node,to_node,distance_m,duration_s,road_type,oneway,name) VALUES (?,?,?,?,?,?,?)",
            edges,
        )
        conn.commit()
        return conn

    def test_direct_route(self, graph_db):
        r = route(graph_db, 12.0, -8.0, 12.5, -7.0)
        assert r.found
        assert r.distance_km > 0
        assert r.duration_min > 0
        assert len(r.nodes) >= 2

    def test_two_hop_route(self, graph_db):
        r = route(graph_db, 12.0, -8.0, 13.0, -6.0)
        assert r.found
        assert r.distance_km > 100  # A→B→C = ~250 km
        assert len(r.nodes) >= 3

    def test_route_via_detour(self, graph_db):
        # A → D goes through B
        r = route(graph_db, 12.0, -8.0, 11.5, -7.0)
        assert r.found
        assert 1 in r.nodes  # start
        assert 2 in r.nodes  # via B
        assert 4 in r.nodes  # end

    def test_no_route(self):
        conn = sqlite3.connect(":memory:")
        conn.row_factory = sqlite3.Row
        conn.executescript(SCHEMA_SQL)
        conn.execute("INSERT INTO nodes VALUES (1, 12.0, -8.0)")
        conn.execute("INSERT INTO nodes VALUES (2, 40.0, 10.0)")
        conn.commit()

        r = route(conn, 12.0, -8.0, 40.0, 10.0)
        assert not r.found

    def test_route_has_geometry(self, graph_db):
        r = route(graph_db, 12.0, -8.0, 13.0, -6.0)
        assert r.geometry["type"] == "LineString"
        assert len(r.geometry["coordinates"]) >= 2

    def test_route_has_steps(self, graph_db):
        r = route(graph_db, 12.0, -8.0, 13.0, -6.0)
        assert len(r.steps) >= 1

    def test_nearest_node(self, graph_db):
        nid = _nearest_node(graph_db, 12.01, -7.99)
        assert nid == 1  # closest to A


# ---------- Route from codes ----------

class TestRouteFromCodes:
    @pytest.fixture
    def ml_db(self):
        """Create graph with nodes at known Yoro code positions."""
        import yoro

        conn = sqlite3.connect(":memory:")
        conn.row_factory = sqlite3.Row
        conn.executescript(SCHEMA_SQL)

        # Create nodes at decoded code positions
        code_a = yoro.encode(12.639, -8.003, domain="ML")
        code_b = yoro.encode(14.489, -4.197, domain="ML")

        da = yoro.decode(code_a)
        db = yoro.decode(code_b)

        conn.execute("INSERT INTO nodes VALUES (1, ?, ?)", (da["lat"], da["lon"]))
        conn.execute("INSERT INTO nodes VALUES (2, ?, ?)", (db["lat"], db["lon"]))
        conn.executemany(
            "INSERT INTO edges (from_node,to_node,distance_m,duration_s,road_type,oneway,name) VALUES (?,?,?,?,?,?,?)",
            [
                (1, 2, 620000, 28000, "primary", 0, "Route Nationale"),
                (2, 1, 620000, 28000, "primary", 0, "Route Nationale"),
            ],
        )
        conn.commit()
        return conn, code_a, code_b

    def test_route_from_codes(self, ml_db):
        from yoromaps.routing import route_from_codes
        conn, code_a, code_b = ml_db
        legs = route_from_codes(conn, [code_a, code_b])
        assert len(legs) == 1
        assert legs[0].found
        assert legs[0].distance_km > 0
