"""
Integration tests with real OSM data.

Downloads a small PBF (Gambia ~3 MB), builds a .yoromaps file,
and tests routing between real locations.

Run with: pytest tests/test_real.py -v -s
"""

import tempfile
from pathlib import Path

import pytest
import yoro

from yoromaps.countries import COUNTRIES, geofabrik_url
from yoromaps.db import open_db, get_metadata
from yoromaps.download import download_pbf, build
from yoromaps.extract import extract_graph
from yoromaps.routing import route, route_from_codes
from yoromaps.tiles import get_tile, tile_count


# Use Gambia for fast tests (~3 MB PBF), Mali for full test
TEST_COUNTRY = "TG"  # Togo — small PBF (~15 MB)


@pytest.fixture(scope="module")
def pbf_path(tmp_path_factory):
    """Download the Gambia PBF once for all tests."""
    tmp = tmp_path_factory.mktemp("pbf")
    path = download_pbf(TEST_COUNTRY, tmp, progress=_progress)
    print(f"\nPBF downloaded: {path} ({path.stat().st_size / 1024 / 1024:.1f} MB)")
    return path


@pytest.fixture(scope="module")
def yoromaps_db(pbf_path, tmp_path_factory):
    """Build a .yoromaps database from the PBF."""
    tmp = tmp_path_factory.mktemp("db")
    db_path = tmp / "gambia.yoromaps"

    conn = open_db(db_path, create=True)
    from yoromaps.db import set_metadata
    set_metadata(conn, "country", TEST_COUNTRY)

    stats = extract_graph(str(pbf_path), conn, progress=_progress)
    print(f"\nGraph extracted: {stats['nodes']} nodes, {stats['edges']} edges")

    set_metadata(conn, "graph_nodes", str(stats["nodes"]))
    set_metadata(conn, "graph_edges", str(stats["edges"]))
    conn.close()

    return db_path


def _progress(msg, current, total):
    if total > 0:
        print(f"  {msg}: {current}/{total}", end="\r")


# ---------- Download ----------

class TestDownload:
    def test_pbf_downloaded(self, pbf_path):
        assert pbf_path.exists()
        assert pbf_path.stat().st_size > 100_000  # At least 100 KB

    def test_pbf_is_valid(self, pbf_path):
        # PBF files start with specific bytes
        with open(pbf_path, "rb") as f:
            header = f.read(4)
        # OSM PBF starts with 0x00 0x00 0x00 (BlobHeader length) or similar
        assert len(header) == 4


# ---------- Extraction ----------

class TestExtraction:
    def test_nodes_exist(self, yoromaps_db):
        conn = open_db(yoromaps_db)
        count = conn.execute("SELECT COUNT(*) FROM nodes").fetchone()[0]
        print(f"\n  Nodes: {count}")
        assert count > 1000  # Gambia should have many road nodes
        conn.close()

    def test_edges_exist(self, yoromaps_db):
        conn = open_db(yoromaps_db)
        count = conn.execute("SELECT COUNT(*) FROM edges").fetchone()[0]
        print(f"\n  Edges: {count}")
        assert count > 1000
        conn.close()

    def test_metadata(self, yoromaps_db):
        conn = open_db(yoromaps_db)
        assert get_metadata(conn, "country") == TEST_COUNTRY
        assert int(get_metadata(conn, "graph_nodes")) > 0
        conn.close()

    def test_road_types_present(self, yoromaps_db):
        conn = open_db(yoromaps_db)
        types = [r[0] for r in conn.execute(
            "SELECT DISTINCT road_type FROM edges"
        ).fetchall()]
        print(f"\n  Road types: {types}")
        assert len(types) >= 3  # At least primary, secondary, residential
        conn.close()

    def test_nodes_in_country_bbox(self, yoromaps_db):
        """All nodes should be within the country's bounding box (roughly)."""
        conn = open_db(yoromaps_db)
        gm = COUNTRIES[TEST_COUNTRY]
        # Allow some margin
        out_of_bounds = conn.execute(
            "SELECT COUNT(*) FROM nodes WHERE lat < ? OR lat > ? OR lon < ? OR lon > ?",
            (gm.bbox[1] - 1, gm.bbox[3] + 1, gm.bbox[0] - 1, gm.bbox[2] + 1)
        ).fetchone()[0]
        total = conn.execute("SELECT COUNT(*) FROM nodes").fetchone()[0]
        assert out_of_bounds < total * 0.01  # Less than 1% out of bounds
        conn.close()


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

class TestRouting:
    def test_route_lome_to_kara(self, yoromaps_db):
        """Route from Lome (south) to Kara (north) — crosses the whole country."""
        conn = open_db(yoromaps_db)
        # Lome: 6.1319, 1.2228
        # Kara: 9.5511, 1.1862
        r = route(conn, 6.1319, 1.2228, 9.5511, 1.1862)
        conn.close()

        print(f"\n  Lome → Kara: {r.distance_km} km, {r.duration_min} min, found={r.found}")
        assert r.found
        assert r.distance_km > 200  # ~410 km by road
        assert r.duration_min > 60
        assert len(r.nodes) > 10
        assert r.geometry["type"] == "LineString"
        assert len(r.geometry["coordinates"]) > 10

    def test_route_short_distance(self, yoromaps_db):
        """Short route within Lome."""
        conn = open_db(yoromaps_db)
        r = route(conn, 6.13, 1.22, 6.14, 1.23)
        conn.close()

        print(f"\n  Short route: {r.distance_km} km, found={r.found}")
        assert r.found
        assert r.distance_km < 20

    def test_route_has_steps(self, yoromaps_db):
        conn = open_db(yoromaps_db)
        r = route(conn, 6.1319, 1.2228, 9.5511, 1.1862)
        conn.close()

        print(f"\n  Steps: {len(r.steps)}")
        assert len(r.steps) > 0
        for step in r.steps:
            assert "instruction" in step
            assert "distance_m" in step

    def test_route_geojson_valid(self, yoromaps_db):
        conn = open_db(yoromaps_db)
        r = route(conn, 6.1319, 1.2228, 9.5511, 1.1862)
        conn.close()

        geom = r.geometry
        assert geom["type"] == "LineString"
        for coord in geom["coordinates"]:
            assert len(coord) == 2
            lon, lat = coord
            assert -180 <= lon <= 180
            assert -90 <= lat <= 90


class TestRouteFromCodes:
    def test_route_from_yoro_codes(self, yoromaps_db):
        """Route between Yoro codes for Lome and Kara."""
        lome_code = yoro.encode(6.1319, 1.2228, domain="TG")
        kara_code = yoro.encode(9.5511, 1.1862, domain="TG")
        print(f"\n  Codes: {lome_code} → {kara_code}")

        conn = open_db(yoromaps_db)
        legs = route_from_codes(conn, [lome_code, kara_code])
        conn.close()

        assert len(legs) == 1
        assert legs[0].found
        assert legs[0].distance_km > 200
        print(f"  Route: {legs[0].distance_km} km, {legs[0].duration_min} min")

    def test_multi_leg_route(self, yoromaps_db):
        """Route through 3 cities: Lome → Sokode → Kara."""
        lome = yoro.encode(6.1319, 1.2228, domain="TG")
        sokode = yoro.encode(8.9833, 1.1333, domain="TG")
        kara = yoro.encode(9.5511, 1.1862, domain="TG")

        conn = open_db(yoromaps_db)
        legs = route_from_codes(conn, [lome, sokode, kara])
        conn.close()

        assert len(legs) == 2
        total_km = sum(leg.distance_km for leg in legs)
        print(f"\n  3-city route: {total_km} km total ({legs[0].distance_km} + {legs[1].distance_km})")
        assert total_km > 200


# ---------- CLI Build ----------

class TestBuild:
    def test_full_build(self, pbf_path):
        """Test the full build() pipeline."""
        with tempfile.TemporaryDirectory() as tmp:
            output = Path(tmp) / "test.yoromaps"
            result = build(
                TEST_COUNTRY,
                output=output,
                pbf_path=pbf_path,
                include_tiles=False,
                progress=_progress,
            )
            assert result.exists()
            assert result.stat().st_size > 100_000

            conn = open_db(result)
            assert get_metadata(conn, "country") == TEST_COUNTRY
            assert int(get_metadata(conn, "graph_nodes")) > 0
            assert int(get_metadata(conn, "graph_edges")) > 0
            conn.close()
