"""
Download OSM data and build/update a .yoromaps file for a country.
"""

from __future__ import annotations

import tempfile
from datetime import datetime, timezone
from email.utils import format_datetime, parsedate_to_datetime
from pathlib import Path

import httpx

from yoromaps.countries import COUNTRIES, geofabrik_url
from yoromaps.db import open_db, get_metadata, set_metadata
from yoromaps.extract import extract_graph


def download_pbf(
    country_code: str,
    output_dir: str | Path,
    progress=None,
    if_newer_than: str | None = None,
) -> Path | None:
    """Download the latest OSM PBF for a country from Geofabrik.

    Args:
        country_code: ISO country code.
        output_dir: Directory to save the PBF file.
        progress: Optional progress callback.
        if_newer_than: HTTP date string. Skips download if server file is not newer.

    Returns:
        Path to the downloaded file, or None if skipped (already up to date).
    """
    url = geofabrik_url(country_code)
    output_dir = Path(output_dir)
    output_dir.mkdir(parents=True, exist_ok=True)
    output_path = output_dir / f"{country_code.lower()}.osm.pbf"

    headers = {}
    if if_newer_than:
        headers["If-Modified-Since"] = if_newer_than

    if progress:
        progress(f"Checking {url}...", 0, 1)

    with httpx.stream("GET", url, follow_redirects=True, timeout=300, headers=headers) as resp:
        if resp.status_code == 304:
            if progress:
                progress("Already up to date", 1, 1)
            return None

        resp.raise_for_status()
        total = int(resp.headers.get("content-length", 0))
        downloaded = 0

        with open(output_path, "wb") as f:
            for chunk in resp.iter_bytes(chunk_size=65536):
                f.write(chunk)
                downloaded += len(chunk)
                if progress and total:
                    progress("Downloading PBF", downloaded, total)

    return output_path


def build(
    country_code: str,
    output: str | Path,
    pbf_path: str | Path | None = None,
    include_tiles: bool = False,
    zoom_min: int = 6,
    zoom_max: int = 12,
    progress=None,
) -> Path:
    """Build a .yoromaps file for a country.

    Args:
        country_code: ISO country code (e.g. "ML", "BF", "CI").
        output: Output .yoromaps file path.
        pbf_path: Path to an existing PBF file. If None, downloads from Geofabrik.
        include_tiles: Also download map tiles (slow, ~200+ MB).
        zoom_min: Min tile zoom level (if include_tiles).
        zoom_max: Max tile zoom level (if include_tiles).
        progress: Optional callable(message, current, total).

    Returns:
        Path to the created .yoromaps file.
    """
    country_code = country_code.upper()
    country = COUNTRIES[country_code]
    output = Path(output)

    # Download PBF if needed
    if pbf_path is None:
        tmp_dir = tempfile.mkdtemp(prefix="yoromaps_")
        pbf_path = download_pbf(country_code, tmp_dir, progress=progress)
    else:
        pbf_path = Path(pbf_path)

    # Create database
    conn = open_db(output, create=True)
    set_metadata(conn, "country", country_code)
    set_metadata(conn, "country_name", country.name)
    set_metadata(conn, "bbox", ",".join(str(v) for v in country.bbox))

    # Extract road graph
    if progress:
        progress("Extracting road graph...", 0, 1)
    stats = extract_graph(pbf_path, conn, progress=progress)
    now = format_datetime(datetime.now(timezone.utc), usegmt=True)
    set_metadata(conn, "graph_nodes", str(stats["nodes"]))
    set_metadata(conn, "graph_edges", str(stats["edges"]))
    set_metadata(conn, "osm_update_date", now)

    if progress:
        progress(f"Graph: {stats['nodes']} nodes, {stats['edges']} edges", 1, 1)

    # Download tiles if requested
    if include_tiles:
        from yoromaps.tiles import download_tiles
        if progress:
            progress("Downloading tiles...", 0, 1)
        n = download_tiles(conn, country.bbox, zoom_min=zoom_min, zoom_max=zoom_max, progress=progress)
        set_metadata(conn, "tiles_count", str(n))

    conn.close()
    return output


def update(
    db_path: str | Path,
    pbf_cache_dir: str | Path | None = None,
    progress=None,
) -> dict:
    """Update an existing .yoromaps file with the latest OSM data.

    Downloads the PBF only if Geofabrik has a newer version.
    Rebuilds the road graph while preserving local POI data.

    Args:
        db_path: Path to the existing .yoromaps file.
        pbf_cache_dir: Directory to cache PBF files. Defaults to same dir as db.
        progress: Optional progress callback.

    Returns:
        Dict with ``updated`` (bool), ``nodes``, ``edges``, ``previous_date``, ``new_date``.
    """
    db_path = Path(db_path)
    conn = open_db(db_path)

    country_code = get_metadata(conn, "country")
    if not country_code:
        conn.close()
        raise ValueError(f"No country metadata in {db_path}. Is this a valid .yoromaps file?")

    last_update = get_metadata(conn, "osm_update_date")
    if pbf_cache_dir is None:
        pbf_cache_dir = db_path.parent

    if progress:
        progress(f"Checking for updates ({country_code})...", 0, 3)

    # Download PBF (only if newer)
    pbf_path = download_pbf(
        country_code,
        pbf_cache_dir,
        progress=progress,
        if_newer_than=last_update,
    )

    if pbf_path is None:
        nodes = int(get_metadata(conn, "graph_nodes") or 0)
        edges = int(get_metadata(conn, "graph_edges") or 0)
        conn.close()
        return {
            "updated": False,
            "nodes": nodes,
            "edges": edges,
            "previous_date": last_update,
            "new_date": last_update,
        }

    # Clear existing graph (keep POI and tiles)
    if progress:
        progress("Clearing old graph...", 1, 3)
    conn.execute("DELETE FROM edges")
    conn.execute("DELETE FROM nodes")
    conn.commit()

    # Re-extract
    if progress:
        progress("Extracting new graph...", 2, 3)
    stats = extract_graph(str(pbf_path), conn, progress=progress)

    now = format_datetime(datetime.now(timezone.utc), usegmt=True)
    set_metadata(conn, "graph_nodes", str(stats["nodes"]))
    set_metadata(conn, "graph_edges", str(stats["edges"]))
    set_metadata(conn, "osm_update_date", now)

    if progress:
        progress("Done", 3, 3)

    conn.close()

    return {
        "updated": True,
        "nodes": stats["nodes"],
        "edges": stats["edges"],
        "previous_date": last_update,
        "new_date": now,
    }
