from __future__ import annotations

import json
import os
from dataclasses import asdict, dataclass, field, replace
from typing import Any, Dict


DEFAULT_CONFIG_PATH = "geodata_config.json"


@dataclass
class RawConfig:
    dgm1_dir: str = "raw/dgm1"
    dop20_dir: str = "raw/dop20/jp2"
    citygml_lod1_dir: str = "raw/citygml/lod1"
    citygml_lod2_dir: str = "raw/citygml/lod2"


@dataclass
class ArchiveConfig:
    dgm1_dir: str = "archive/dgm1"
    dop20_dir: str = "archive/dop20"
    citygml_lod1_dir: str = "archive/citygml/lod1"
    citygml_lod2_dir: str = "archive/citygml/lod2"


@dataclass
class WorkConfig:
    work_dir: str = "work"
    heightmap_vrt: str = "work/dgm.vrt"
    ortho_vrt: str = "work/dop.vrt"


@dataclass
class ExportConfig:
    heightmap_dir: str = "export_unity/height_png16"
    ortho_dir: str = "export_unity/ortho_jpg"
    manifest_path: str = "export_unity/tile_index.csv"


@dataclass
class HeightmapConfig:
    out_res: int = 1025
    resample: str = "bilinear"
    tile_size_m: int = 1000


@dataclass
class OrthoConfig:
    out_res: int = 2048
    jpeg_quality: int = 90


@dataclass
class BuildingConfig:
    out_dir: str = "export_unity/buildings_tiles"
    work_cityjson_dir: str = "work/cityjson_lod2"
    work_rebased_dir: str = "work/cityjson_lod2_local"
    work_glb_dir: str = "work/buildings_glb_tmp"
    triangle_budget_min: int = 200000
    triangle_budget_max: int = 350000
    roof_unlit: bool = True


@dataclass
class TreeConfig:
    csv_dir: str = "export_unity/trees"
    glb_dir: str = "export_unity/trees_tiles"
    proxy_library: str = "export_unity/tree_proxies.glb"
    grid_res_m: float = 2.0
    min_height_m: float = 2.0
    max_trees: int = 5000
    chunk_grid: int = 4
    proxy_variants: int = 16
    proxy_min_tris: int = 120
    proxy_max_tris: int = 180
    building_buffer_m: float = 1.5
    instancing: bool = False


@dataclass
class PointCloudConfig:
    """Configuration for point cloud data sources."""
    lpg_dir: str = "raw/lpg"
    lpo_dir: str = "raw/lpo"
    bdom_dir: str = "raw/bdom20rgbi"
    dom1_dir: str = "raw/dom1"
    chunk_size: int = 5_000_000
    use_lpg_validation: bool = True
    use_lpo_refinement: bool = True


@dataclass
class EnhancedHeightmapConfig:
    """Configuration for enhanced heightmap export with LPG validation."""
    lpg_validation_threshold_m: float = 0.5
    export_quality_map: bool = True
    quality_map_dir: str = "export_unity/height_quality"


@dataclass
class EnhancedBuildingConfig:
    """Configuration for enhanced building export with point cloud fusion."""
    out_dir: str = "export_unity/buildings_enhanced"
    ransac_threshold_m: float = 0.15
    ransac_iterations: int = 1000
    max_roof_planes: int = 6
    min_plane_inliers: int = 50
    use_bdom_roof: bool = True
    use_lpo_refinement: bool = True
    use_dop20_textures: bool = True
    fallback_to_citygml: bool = True


@dataclass
class StreetFurnitureConfig:
    """Configuration for street furniture detection."""
    enabled: bool = True
    csv_dir: str = "export_unity/street_furniture"
    min_height_m: float = 0.3
    max_height_m: float = 8.0
    min_confidence: float = 0.6
    use_lpo_refinement: bool = True
    lamp_height_range: tuple = (4.0, 8.0)
    bench_height_range: tuple = (0.4, 1.0)
    sign_height_range: tuple = (2.0, 4.0)
    bollard_height_range: tuple = (0.5, 1.2)


@dataclass
class EnhancedTreeConfig:
    """Configuration for enhanced tree detection."""
    csv_dir: str = "export_unity/trees_enhanced"
    glb_dir: str = "export_unity/trees_enhanced_tiles"
    exclude_furniture: bool = True
    use_lpo_refinement: bool = True
    sample_canopy_color: bool = True
    lpo_search_radius_m: float = 5.0
    canopy_sample_radius_factor: float = 0.5


@dataclass
class Config:
    raw: RawConfig = field(default_factory=RawConfig)
    archives: ArchiveConfig = field(default_factory=ArchiveConfig)
    work: WorkConfig = field(default_factory=WorkConfig)
    export: ExportConfig = field(default_factory=ExportConfig)
    heightmap: HeightmapConfig = field(default_factory=HeightmapConfig)
    ortho: OrthoConfig = field(default_factory=OrthoConfig)
    buildings: BuildingConfig = field(default_factory=BuildingConfig)
    trees: TreeConfig = field(default_factory=TreeConfig)
    # Enhanced pipeline configs
    pointcloud: PointCloudConfig = field(default_factory=PointCloudConfig)
    heightmap_enhanced: EnhancedHeightmapConfig = field(default_factory=EnhancedHeightmapConfig)
    buildings_enhanced: EnhancedBuildingConfig = field(default_factory=EnhancedBuildingConfig)
    street_furniture: StreetFurnitureConfig = field(default_factory=StreetFurnitureConfig)
    trees_enhanced: EnhancedTreeConfig = field(default_factory=EnhancedTreeConfig)

    @classmethod
    def default(cls) -> "Config":
        return cls()

    @classmethod
    def load(cls, path: str = DEFAULT_CONFIG_PATH) -> "Config":
        with open(path, "r", encoding="utf-8") as f:
            data = json.load(f)
        return cls.from_dict(data)

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "Config":
        return cls(
            raw=RawConfig(**data["raw"]),
            archives=ArchiveConfig(**data["archives"]),
            work=WorkConfig(**data["work"]),
            export=ExportConfig(**data["export"]),
            heightmap=HeightmapConfig(**data["heightmap"]),
            ortho=OrthoConfig(**data["ortho"]),
            buildings=BuildingConfig(**data.get("buildings", {})),
            trees=TreeConfig(**data.get("trees", {})),
            # Enhanced pipeline configs (with defaults for backward compat)
            pointcloud=PointCloudConfig(**data.get("pointcloud", {})),
            heightmap_enhanced=EnhancedHeightmapConfig(**data.get("heightmap_enhanced", {})),
            buildings_enhanced=EnhancedBuildingConfig(**data.get("buildings_enhanced", {})),
            street_furniture=StreetFurnitureConfig(**data.get("street_furniture", {})),
            trees_enhanced=EnhancedTreeConfig(**data.get("trees_enhanced", {})),
        )

    def to_dict(self) -> Dict[str, Any]:
        return asdict(self)

    def save(self, path: str = DEFAULT_CONFIG_PATH) -> None:
        with open(path, "w", encoding="utf-8") as f:
            json.dump(self.to_dict(), f, indent=2)

    def with_overrides(self, raw_dgm1_path: str | None = None, raw_dop20_path: str | None = None) -> "Config":
        cfg = self
        if raw_dgm1_path:
            cfg = replace(cfg, raw=replace(cfg.raw, dgm1_dir=raw_dgm1_path))
        if raw_dop20_path:
            cfg = replace(cfg, raw=replace(cfg.raw, dop20_dir=raw_dop20_path))
        return cfg


def ensure_default_config(path: str = DEFAULT_CONFIG_PATH) -> Config:
    if not os.path.exists(path):
        cfg = Config.default()
        cfg.save(path)
        return cfg
    return Config.load(path)
