from __future__ import annotations

import configparser
import json
from dataclasses import dataclass
from pathlib import Path
from typing import Any

from .paths import EnvironmentPaths


MOD_PREFIXES = {
    "aegis",
    "alm",
    "amy",
    "arch",
    "art",
    "bati",
    "bksy",
    "ddm",
    "ecf",
    "gmp",
    "hsrc",
    "j8",
    "ltkaeri",
    "naz",
    "p3",
    "rize",
    "slang",
    "srp",
    "wm",
}


def _load_json(path: Path) -> dict[str, Any] | None:
    text = None
    try:
        text = path.read_text(encoding="utf-8-sig")
        return json.loads(text)
    except Exception:
        if text is None:
            return None
    sanitized_chars: list[str] = []
    in_string = False
    escaped = False
    for char in text:
        if in_string:
            if escaped:
                sanitized_chars.append(char)
                escaped = False
                continue
            if char == "\\":
                sanitized_chars.append(char)
                escaped = True
                continue
            if char == '"':
                sanitized_chars.append(char)
                in_string = False
                continue
            if char == "\n":
                sanitized_chars.append("\\n")
                continue
            if char == "\r":
                sanitized_chars.append("\\r")
                continue
            if char == "\t":
                sanitized_chars.append("\\t")
                continue
            if ord(char) < 32:
                sanitized_chars.append(" ")
                continue
            sanitized_chars.append(char)
            continue
        sanitized_chars.append(char)
        if char == '"':
            in_string = True
            escaped = False
    try:
        return json.loads("".join(sanitized_chars))
    except Exception:
        return None


def _parse_number_from_spec(value: Any) -> float | None:
    if value is None:
        return None
    if isinstance(value, (int, float)):
        return float(value)
    text = str(value).strip().lower()
    if not text or text == "--":
        return None
    digits = []
    decimal_seen = False
    for char in text:
        if char.isdigit():
            digits.append(char)
        elif char == "." and not decimal_seen:
            digits.append(char)
            decimal_seen = True
        elif digits:
            break
    if not digits:
        return None
    try:
        number = float("".join(digits))
    except ValueError:
        return None
    if "km" in text and number < 100:
        return number * 1000.0
    return number


def _drive_layout(tags: list[str]) -> str | None:
    normalized = {tag.strip().lower().lstrip("#") for tag in tags}
    for candidate in ("fwd", "rwd", "awd", "4wd"):
        if candidate in normalized:
            return candidate
    return None


def _transmission(tags: list[str]) -> str | None:
    normalized = {tag.strip().lower().lstrip("#") for tag in tags}
    for candidate in ("manual", "semiautomatic", "automatic"):
        if candidate in normalized:
            return candidate
    return None


def _official_car(folder_name: str) -> bool:
    parts = folder_name.split("_")
    if parts and parts[0].lower() in MOD_PREFIXES:
        return False
    return True


@dataclass(slots=True)
class SetupSeed:
    car_model: str
    path: Path
    scope: str


@dataclass(slots=True)
class CarInfo:
    folder_name: str
    name: str
    brand: str | None
    tags: list[str]
    car_class: str | None
    drive_layout: str | None
    transmission: str | None
    bhp: float | None
    torque_nm: float | None
    weight_kg: float | None
    top_speed_kmh: float | None
    power_to_weight_kg_per_hp: float | None
    official: bool
    setup_seed_path: Path | None
    setup_count: int

    @property
    def has_setup_seed(self) -> bool:
        return self.setup_seed_path is not None


@dataclass(slots=True)
class TrackLayoutInfo:
    track: str
    layout: str | None
    layout_id: str
    name: str
    length_m: float | None
    tags: list[str]
    run: str | None

    @property
    def training_suitable(self) -> bool:
        normalized = {tag.lower() for tag in self.tags}
        if "drag" in normalized or "highway" in normalized or "hillclimb" in normalized:
            return False
        return True


@dataclass(slots=True)
class ContentLibrary:
    cars: dict[str, CarInfo]
    tracks: dict[str, TrackLayoutInfo]
    setup_seeds: dict[str, list[SetupSeed]]

    def summary(self) -> dict[str, Any]:
        return {
            "car_count": len(self.cars),
            "track_layout_count": len(self.tracks),
            "cars_with_setup_seed": sum(1 for car in self.cars.values() if car.has_setup_seed),
        }


def discover_setup_seeds(paths: EnvironmentPaths) -> dict[str, list[SetupSeed]]:
    seeds: dict[str, list[SetupSeed]] = {}
    setups_root = paths.documents_root / "setups"
    if not setups_root.exists():
        return seeds
    for car_dir in setups_root.iterdir():
        if not car_dir.is_dir():
            continue
        car_name = car_dir.name
        for ini in car_dir.rglob("*.ini"):
            scope = "track" if "generic" not in ini.parts else "generic"
            seeds.setdefault(car_name, []).append(
                SetupSeed(car_model=car_name, path=ini, scope=scope)
            )
        seeds.setdefault(car_name, []).sort(
            key=lambda seed: (seed.scope != "generic", seed.path.name != "last.ini", str(seed.path))
        )
    return seeds


def build_content_library(paths: EnvironmentPaths) -> ContentLibrary:
    if paths.assetto_root is None:
        raise RuntimeError("Assetto Corsa install was not found")

    setup_seeds = discover_setup_seeds(paths)
    cars: dict[str, CarInfo] = {}
    tracks: dict[str, TrackLayoutInfo] = {}

    cars_root = paths.assetto_root / "content" / "cars"
    for car_dir in cars_root.iterdir():
        ui_path = car_dir / "ui" / "ui_car.json"
        if not ui_path.exists():
            continue
        data = _load_json(ui_path)
        if data is None:
            continue
        specs = data.get("specs", {})
        seeds = setup_seeds.get(car_dir.name, [])
        cars[car_dir.name] = CarInfo(
            folder_name=car_dir.name,
            name=data.get("name", car_dir.name),
            brand=data.get("brand"),
            tags=[str(tag) for tag in data.get("tags", [])],
            car_class=data.get("class"),
            drive_layout=_drive_layout(data.get("tags", [])),
            transmission=_transmission(data.get("tags", [])),
            bhp=_parse_number_from_spec(specs.get("bhp")),
            torque_nm=_parse_number_from_spec(specs.get("torque")),
            weight_kg=_parse_number_from_spec(specs.get("weight")),
            top_speed_kmh=_parse_number_from_spec(specs.get("topspeed")),
            power_to_weight_kg_per_hp=_parse_number_from_spec(specs.get("pwratio")),
            official=_official_car(car_dir.name),
            setup_seed_path=seeds[0].path if seeds else None,
            setup_count=len(seeds),
        )

    tracks_root = paths.assetto_root / "content" / "tracks"
    for ui_path in tracks_root.rglob("ui_track.json"):
        data = _load_json(ui_path)
        if data is None:
            continue
        rel = ui_path.relative_to(tracks_root)
        parts = rel.parts
        track = parts[0]
        layout = parts[2] if len(parts) >= 4 and parts[1] == "ui" else None
        layout_id = track if layout is None else f"{track}/{layout}"
        tracks[layout_id] = TrackLayoutInfo(
            track=track,
            layout=layout,
            layout_id=layout_id,
            name=data.get("name", layout_id),
            length_m=_parse_number_from_spec(data.get("length")),
            tags=[str(tag) for tag in data.get("tags", [])],
            run=data.get("run"),
        )

    return ContentLibrary(cars=cars, tracks=tracks, setup_seeds=setup_seeds)


def load_setup_file(path: Path) -> configparser.ConfigParser:
    parser = configparser.ConfigParser()
    parser.optionxform = str
    parser.read(path, encoding="utf-8")
    return parser
