"""
Valhalla tile lifecycle management.

Handles building, caching, and packing of Valhalla routing tiles from OSM
PBF data.  Separated from the matcher to keep infrastructure concerns
independent of matching logic.
"""

import json
import logging
import os
import shutil
import subprocess
import sys
import tarfile
from pathlib import Path
from typing import Optional

from walkingpandas._utils import generate_cache_key

logger = logging.getLogger(__name__)


def get_cache_dir(custom_dir: Optional[os.PathLike] = None) -> Path:
    """Resolve the directory used for caching tiles and OSM data.

    Resolution order:
      1. *custom_dir* argument
      2. ``WALKINGPANDAS_CACHE_DIR`` environment variable
      3. ``./cache/walkingpandas/valhalla`` (relative to cwd)

    Args:
        custom_dir: Explicit cache directory override.

    Returns:
        Path: Resolved cache directory.
    """
    if custom_dir:
        return Path(custom_dir)
    env = os.environ.get("WALKINGPANDAS_CACHE_DIR")
    if env:
        return Path(env)
    return Path.cwd() / "cache" / "walkingpandas" / "valhalla"


def write_valhalla_config(config_path: Path, tile_dir: Path) -> None:
    """Write the minimal ``valhalla.json`` required for tile building.

    Args:
        config_path: Destination path for the config file.
        tile_dir: Directory where Valhalla tiles are stored.
    """
    config = {
        "mjolnir": {
            "tile_dir": str(tile_dir.resolve()),
            "max_cache_size": 1_000_000_000,
            "id_table_size": 1_300_000_000,
            "use_lru_mem_cache": False,
            "hierarchy": True,
            "shortcuts": True,
            "include_pedestrian": True,
            "include_bicycle": True,
            "include_driving": True,
            "data_processing": {
                "infer_internal_intersections": True,
                "infer_turn_channels": True,
                "apply_country_overrides": True,
                "use_admin_db": False,
            },
        }
    }
    config_path.write_text(json.dumps(config, indent=2))


def build_tiles(config_path: Path, pbf_path: Path) -> None:
    """Run the PyValhalla tile builder sub-process.

    Args:
        config_path: Path to the ``valhalla.json`` config.
        pbf_path: Path to the OSM PBF input file.

    Raises:
        RuntimeError: If the build fails or ``valhalla_build_tiles`` is
            not found.
    """
    logger.debug(
        "Building Valhalla tiles from %s (this may take a few minutes)...",
        pbf_path.name,
    )

    try:
        subprocess.run(
            [
                sys.executable,
                "-m",
                "valhalla",
                "valhalla_build_tiles",
                "-c",
                str(config_path),
                str(pbf_path),
            ],
            check=True,
            capture_output=True,
            text=True,
            timeout=3600,
        )
        logger.debug("Successfully built Valhalla tiles.")
    except subprocess.CalledProcessError as e:
        logger.error(
            "Tile building failed!\nSTDOUT:\n%s\nSTDERR:\n%s", e.stdout, e.stderr
        )
        raise RuntimeError(
            f"valhalla_build_tiles failed. See logs above. Error: {e}"
        ) from e
    except FileNotFoundError:
        raise RuntimeError(
            "valhalla_build_tiles not found. Ensure pyvalhalla is installed."
        ) from None


def pack_to_tar(tile_dir: Path, tar_path: Path) -> None:
    """Pack the generated tile directory into a portable ``.tar`` file.

    Args:
        tile_dir: Directory containing the built tiles.
        tar_path: Destination path for the ``.tar`` archive.
    """
    logger.debug("Packing tiles into %s ...", tar_path.name)
    with tarfile.open(tar_path, "w") as tf:
        for f in tile_dir.rglob("*"):
            if f.is_file():
                tf.add(str(f), arcname=f.relative_to(tile_dir).as_posix())


def ensure_tiles(
    osm_pbf: os.PathLike,
    cache_dir: Optional[os.PathLike] = None,
) -> Path:
    """Return the path to a Valhalla tile archive for the given OSM PBF.

    Builds tiles from scratch on a cache miss; returns the cached archive
    on a hit.

    Args:
        osm_pbf: Path to a ``.osm.pbf`` file.
        cache_dir: Optional override for the tile cache directory.

    Returns:
        Path to the cached ``.tar`` tile archive.

    Raises:
        ValueError: If *osm_pbf* is None.
        FileNotFoundError: If *osm_pbf* does not exist.
    """
    if osm_pbf is None:
        raise ValueError("osm_pbf is required.")

    cache_path = get_cache_dir(cache_dir)
    cache_path.mkdir(parents=True, exist_ok=True)

    key = generate_cache_key(osm_pbf=osm_pbf)
    tar_path = cache_path / f"{key}.tar"

    if tar_path.exists():
        logger.debug("Using cached Valhalla tiles from %s", tar_path)
        return tar_path

    logger.debug("Cache miss for %s. Starting tile build process...", key)

    pbf_path = Path(osm_pbf).resolve()
    if not pbf_path.exists():
        raise FileNotFoundError(f"Provided osm_pbf not found: {pbf_path}")

    tile_dir = cache_path / key
    tile_dir.mkdir(parents=True, exist_ok=True)
    config_path = tile_dir / "valhalla.json"

    write_valhalla_config(config_path, tile_dir)
    build_tiles(config_path, pbf_path)
    pack_to_tar(tile_dir, tar_path)

    shutil.rmtree(tile_dir, ignore_errors=True)
    logger.debug("Tiles successfully built and cached at %s", tar_path)

    return tar_path
