from __future__ import annotations

import argparse
from dataclasses import asdict, dataclass, field, fields
from pathlib import Path
from typing import Any, get_args, get_origin, get_type_hints


@dataclass
class ModelConfig:
    candidate_routes: str = "data/Mandl/Mandl_route.txt"
    dataset_name: str = "Mandl"
    route_num: int = 5
    od_num: int = 30
    freq_set: list[float] = field(default_factory=lambda: [0.0, 1.0, 2.0, 4.0, 6.0])
    demand_coef: float = 1.0
    tau: float = 0.5
    s_bar: float = 1.0
    c_bar: float = 3.0
    Psi0: float = 0.0
    time_limit: int = 3600
    accelerate: bool = False
    vot: float = 12.0
    mu: float = 0.15
    beta_board: float = 1.0
    beta_transit: float = 1.0
    gamma_board: float = 0.2
    gamma_transit: float = 1.2
    exponent_n: int = 2
    capacity: float = 20.0

    def resolved_candidate_routes(self, repo_root: Path) -> Path:
        path = Path(self.candidate_routes)
        return path if path.is_absolute() else repo_root / path

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

    def with_overrides(self, **overrides: Any) -> ModelConfig:
        return ModelConfig(**(self.to_dict() | overrides))


def add_model_config_arguments(
    parser: argparse.ArgumentParser,
    *,
    include: set[str] | None = None,
    exclude: set[str] | None = None,
) -> None:
    type_hints = get_type_hints(ModelConfig)
    defaults = ModelConfig()

    for config_field in fields(ModelConfig):
        if include is not None and config_field.name not in include:
            continue
        if exclude is not None and config_field.name in exclude:
            continue

        config_type = type_hints[config_field.name]
        arg_kwargs: dict[str, Any] = {
            "dest": config_field.name,
            "default": argparse.SUPPRESS,
            "help": f"Override ModelConfig.{config_field.name} (default: {getattr(defaults, config_field.name)!r}).",
        }
        origin = get_origin(config_type)

        if config_type is bool:
            arg_kwargs["action"] = argparse.BooleanOptionalAction
        elif origin is list:
            item_type = get_args(config_type)[0]
            arg_kwargs["type"] = item_type
            arg_kwargs["nargs"] = "+"
        else:
            arg_kwargs["type"] = config_type

        parser.add_argument(f"--{config_field.name.replace('_', '-')}", **arg_kwargs)


def model_config_from_namespace(namespace: argparse.Namespace) -> ModelConfig:
    valid_names = {config_field.name for config_field in fields(ModelConfig)}
    overrides = {name: value for name, value in vars(namespace).items() if name in valid_names}
    return ModelConfig(**overrides)
