#!/usr/bin/env python3
"""CLI entrypoint to export GeoData assets for Unity."""
from __future__ import annotations

import argparse
import os
import sys
from typing import Iterable

try:
    import tomllib
except ImportError:  # pragma: no cover - tomllib is required
    tomllib = None

from geodata_download import run_download

from geodata_pipeline.config import Config, DEFAULT_CONFIG_PATH, ensure_default_config
from geodata_pipeline.buildings import export_buildings
from geodata_pipeline.buildings_enhanced import export_buildings_enhanced
from geodata_pipeline.heightmaps import export_heightmaps
from geodata_pipeline.heightmaps_enhanced import export_heightmaps_enhanced
from geodata_pipeline.lpolpg_split import split_lpolpg
from geodata_pipeline.orthophotos import export_orthophotos
from geodata_pipeline.river_erosion import erode_rivers
from geodata_pipeline.setup_helpers import ensure_directories, materialize_archives
from geodata_pipeline.street_furniture import export_street_furniture
from geodata_pipeline.trees import export_trees
from geodata_pipeline.trees_enhanced import export_trees_enhanced


def parse_args(argv: Iterable[str] | None = None) -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Export GeoData assets for Unity.")
    parser.add_argument(
        "--config",
        default=DEFAULT_CONFIG_PATH,
        help="Path to config TOML (created on --setup if missing).",
    )
    parser.add_argument(
        "--export",
        choices=[
            "heightmap", "textures", "buildings", "trees", "all",
            "heightmap-enhanced", "buildings-enhanced", "trees-enhanced",
            "street-furniture", "all-enhanced"
        ],
        default=None,
        help="Which assets to export. Enhanced options use point cloud data.",
    )
    parser.add_argument("--raw-dgm1-path", dest="raw_dgm1_path", help="Override raw DGM1 directory.")
    parser.add_argument("--raw-dop20-path", dest="raw_dop20_path", help="Override raw DOP20 JP2 directory.")
    parser.add_argument(
        "--build-from-archive",
        action="store_true",
        help="Populate raw inputs from archives (unzips zips, leaves dop20 filelist in archive).",
    )
    parser.add_argument(
        "--setup",
        action="store_true",
        help="Create default directory structure and config if missing; does not export.",
    )
    parser.add_argument(
        "--force-vrt",
        action="store_true",
        help="Rebuild VRTs even if present (useful after moving raw data).",
    )
    parser.add_argument(
        "--download",
        action="store_true",
        help="Download raw datasets before export using geodata_download.toml.",
    )
    parser.add_argument(
        "--download-config",
        default="geodata_download.toml",
        help="Path to download config TOML.",
    )
    parser.add_argument(
        "--download-datasets",
        help="Comma-separated dataset keys to download (default: enabled datasets).",
    )
    parser.add_argument(
        "--download-start",
        nargs=2,
        type=int,
        metavar=("X", "Y"),
        help="Override tile range start for download (x y).",
    )
    parser.add_argument(
        "--download-end",
        nargs=2,
        type=int,
        metavar=("X", "Y"),
        help="Override tile range end for download (x y).",
    )
    parser.add_argument(
        "--clean-downloads",
        action="store_true",
        help="Delete selected dataset folders before downloading.",
    )
    parser.add_argument(
        "--download-ca-bundle",
        help="Override CA bundle path for downloads.",
    )
    parser.add_argument(
        "--split-lpolpg",
        action="store_true",
        help="Split combined lpolpg LAZ into LPG/LPO outputs.",
    )
    parser.add_argument(
        "--split-lpolpg-formats",
        default="laz,xyz",
        help="Comma-separated output formats for lpolpg split (laz,xyz).",
    )
    parser.add_argument(
        "--split-lpolpg-ground-classes",
        default="2",
        help="Comma-separated LAS classification codes treated as ground.",
    )
    parser.add_argument(
        "--split-lpolpg-overwrite",
        action="store_true",
        help="Overwrite existing LPG/LPO outputs when splitting lpolpg.",
    )
    parser.add_argument(
        "--split-lpolpg-delete-source",
        action="store_true",
        help="Delete lpolpg source files after a successful split.",
    )
    parser.add_argument(
        "--erode-rivers",
        action="store_true",
        help="Erode heightmap tiles using HydroRIVERS after heightmap export (requires tile_index.csv).",
    )
    return parser.parse_args(argv)


def load_config(args: argparse.Namespace) -> Config:
    if args.setup:
        cfg = Config.default()
        cfg.save(args.config)
        return cfg.with_overrides(raw_dgm1_path=args.raw_dgm1_path, raw_dop20_path=args.raw_dop20_path)
    if os.path.exists(args.config):
        cfg = Config.load(args.config)
    else:
        cfg = Config.default()
        cfg.save(args.config)
    return cfg.with_overrides(raw_dgm1_path=args.raw_dgm1_path, raw_dop20_path=args.raw_dop20_path)


def _download_requests_lpolpg(download_config: str, requested: list[str] | None) -> bool:
    lpolpg_keys = {"lpolpg", "lpg", "lpo"}
    if requested:
        return any(name in lpolpg_keys for name in requested)
    if tomllib is None:
        return False
    try:
        with open(download_config, "rb") as fh:
            cfg = tomllib.load(fh)
    except OSError:
        return False
    datasets = cfg.get("datasets", {})
    if not isinstance(datasets, dict):
        return False
    for key in lpolpg_keys:
        dataset_cfg = datasets.get(key)
        if isinstance(dataset_cfg, dict) and dataset_cfg.get("enabled", True):
            return True
    return False


def main(argv: Iterable[str] | None = None) -> int:
    args = parse_args(argv)
    cfg = load_config(args)
    target_export = None
    action_flags = args.download or args.split_lpolpg or args.erode_rivers
    if args.export is not None:
        target_export = args.export
    elif not action_flags:
        target_export = "all"

    if args.setup:
        ensure_directories(cfg)
        print(f"Directories ensured. Config at {args.config}.")
        if args.build_from_archive:
            materialize_archives(cfg)
        if args.export is None and not args.download:
            return 0

    if args.build_from_archive and not args.setup:
        materialize_archives(cfg)

    if args.download:
        datasets = (
            [name.strip() for name in args.download_datasets.split(",")]
            if args.download_datasets
            else None
        )
        start = tuple(args.download_start) if args.download_start else None
        end = tuple(args.download_end) if args.download_end else None
        if (start is None) != (end is None):
            raise SystemExit("--download-start and --download-end must be provided together.")
        download_exit = run_download(
            config_path=args.download_config,
            requested_datasets=datasets,
            start_override=start,
            end_override=end,
            clean_downloads=args.clean_downloads,
            ca_bundle_override=args.download_ca_bundle,
        )
        if download_exit != 0:
            return download_exit
        if not args.split_lpolpg and _download_requests_lpolpg(args.download_config, datasets):
            print("[download] lpolpg detected; splitting into lpg/lpo.")
            args.split_lpolpg = True
    if args.split_lpolpg:
        formats = [fmt.strip() for fmt in args.split_lpolpg_formats.split(",") if fmt.strip()]
        ground = [int(val) for val in args.split_lpolpg_ground_classes.split(",") if val.strip()]
        split_exit = split_lpolpg(
            cfg,
            formats=formats,
            ground_classes=ground,
            overwrite=args.split_lpolpg_overwrite,
            delete_source=args.split_lpolpg_delete_source,
        )
        if split_exit != 0:
            return split_exit

    exit_codes = []

    if target_export is not None:
        # Standard exports
        if target_export in ("heightmap", "all"):
            exit_codes.append(export_heightmaps(cfg, force_vrt=args.force_vrt))
        if target_export in ("textures", "all"):
            exit_codes.append(export_orthophotos(cfg, force_vrt=args.force_vrt))
        if target_export in ("buildings", "all"):
            exit_codes.append(export_buildings(cfg))
        if target_export in ("trees", "all"):
            exit_codes.append(export_trees(cfg, force_vrt=args.force_vrt))

        # Enhanced exports (use point cloud data)
        # Order matters: heightmap-enhanced creates tile_index.csv needed by others
        # street-furniture must run before trees-enhanced (for exclusion mask)
        if target_export in ("heightmap-enhanced", "all-enhanced"):
            exit_codes.append(export_heightmaps_enhanced(cfg))
        if target_export in ("all-enhanced",):
            exit_codes.append(export_orthophotos(cfg, force_vrt=args.force_vrt))
        if target_export in ("street-furniture", "all-enhanced"):
            exit_codes.append(export_street_furniture(cfg))
        if target_export in ("buildings-enhanced", "all-enhanced"):
            exit_codes.append(export_buildings_enhanced(cfg))
        if target_export in ("trees-enhanced", "all-enhanced"):
            exit_codes.append(export_trees_enhanced(cfg))

    if args.erode_rivers:
        exit_codes.append(erode_rivers(cfg))

    return max(exit_codes) if exit_codes else 0


if __name__ == "__main__":
    sys.exit(main())
