from __future__ import annotations

import os
import re
from collections.abc import Mapping
from dataclasses import dataclass
from pathlib import Path
from urllib.parse import urlparse

from dotenv import dotenv_values

from .errors import SettingsError


def _load_dotenv(path: Path) -> dict[str, str]:
    return {
        key: value
        for key, value in dotenv_values(path).items()
        if value is not None
    }


def _parse_bool(value: str | None, *, default: bool) -> bool:
    if value is None or value == "":
        return default
    normalized = value.strip().lower()
    if normalized in {"1", "true", "yes", "on"}:
        return True
    if normalized in {"0", "false", "no", "off"}:
        return False
    raise SettingsError(f"Invalid boolean value: {value!r}")


def _parse_int(value: str | None, *, default: int) -> int:
    if value is None or value == "":
        return default
    try:
        return int(value)
    except ValueError as exc:
        raise SettingsError(f"Invalid integer value: {value!r}") from exc


@dataclass(frozen=True)
class LiveSafetyPolicy:
    prevent_create: bool
    enable_test_mode: bool
    test_node: str | None
    test_pool: str | None
    test_tag: str
    test_vm_name_prefix: str
    keep_failed_vm: bool

    def validate(self) -> None:
        if self.enable_test_mode:
            if not self.test_node:
                raise SettingsError(
                    "PROXMOX_TEST_NODE is required when PROXMOX_ENABLE_TEST_MODE=true."
                )
            if not self.test_pool:
                raise SettingsError(
                    "PROXMOX_TEST_POOL is required when PROXMOX_ENABLE_TEST_MODE=true."
                )
            if not self.test_tag:
                raise SettingsError(
                    "PROXMOX_TEST_TAG is required when PROXMOX_ENABLE_TEST_MODE=true."
                )
            if not self.test_vm_name_prefix:
                raise SettingsError(
                    "PROXMOX_TEST_VM_NAME_PREFIX is required when "
                    "PROXMOX_ENABLE_TEST_MODE=true."
                )

    @property
    def allow_create(self) -> bool:
        return not self.prevent_create

    def effective_vm_name(self, requested_name: str) -> str:
        if not self.enable_test_mode:
            return requested_name
        if requested_name.startswith(self.test_vm_name_prefix):
            return requested_name
        return f"{self.test_vm_name_prefix}{requested_name}"


@dataclass(frozen=True)
class AppSettings:
    proxmox_url: str | None
    proxmox_api_base: str
    proxmox_user: str | None
    proxmox_password: str | None
    proxmox_realm: str | None
    proxmox_verify_tls: bool
    request_timeout_seconds: int
    default_iso_selector: str | None
    safety_policy: LiveSafetyPolicy

    @classmethod
    def from_env(
        cls,
        env: Mapping[str, str] | None = None,
        *,
        load_dotenv_file: bool = True,
        dotenv_path: str | Path = ".env",
        config_dotenv_path: str | Path | None = None,
    ) -> AppSettings:
        raw: dict[str, str] = {}
        if load_dotenv_file:
            cwd_dotenv = Path(dotenv_path)
            home_config_dotenv = (
                Path(config_dotenv_path).expanduser()
                if config_dotenv_path is not None
                else Path.home() / ".config" / "pve-vm-setup" / ".env"
            )
            raw.update(_load_dotenv(cwd_dotenv))
            raw.update(_load_dotenv(home_config_dotenv))
        raw.update(os.environ if env is None else env)

        api_base = raw.get("PROXMOX_API_BASE", "/api2/json").strip() or "/api2/json"
        if not api_base.startswith("/"):
            api_base = f"/{api_base}"

        safety_policy = LiveSafetyPolicy(
            prevent_create=_parse_bool(raw.get("PROXMOX_PREVENT_CREATE"), default=False),
            enable_test_mode=_parse_bool(raw.get("PROXMOX_ENABLE_TEST_MODE"), default=False),
            test_node=raw.get("PROXMOX_TEST_NODE") or None,
            test_pool=raw.get("PROXMOX_TEST_POOL") or None,
            test_tag=raw.get("PROXMOX_TEST_TAG", "codex-e2e").strip() or "codex-e2e",
            test_vm_name_prefix=raw.get("PROXMOX_TEST_VM_NAME_PREFIX", "codex-e2e-").strip()
            or "codex-e2e-",
            keep_failed_vm=_parse_bool(raw.get("PROXMOX_KEEP_FAILED_VM"), default=True),
        )
        safety_policy.validate()

        proxmox_url = (raw.get("PROXMOX_URL") or "").strip() or None
        if proxmox_url is not None:
            proxmox_url = proxmox_url.rstrip("/")
        default_iso_selector = (raw.get("PROXMOX_DEFAULT_ISO_SELECTOR") or "").strip() or None
        if default_iso_selector and default_iso_selector.startswith("regex:"):
            try:
                re.compile(default_iso_selector.removeprefix("regex:"))
            except re.error as exc:
                raise SettingsError(
                    "Invalid PROXMOX_DEFAULT_ISO_SELECTOR regex."
                ) from exc

        return cls(
            proxmox_url=proxmox_url,
            proxmox_api_base=api_base,
            proxmox_user=(raw.get("PROXMOX_USER") or "").strip() or None,
            proxmox_password=raw.get("PROXMOX_PASSWORD") or None,
            proxmox_realm=(raw.get("PROXMOX_REALM") or "").strip() or None,
            proxmox_verify_tls=_parse_bool(raw.get("PROXMOX_VERIFY_TLS"), default=False),
            request_timeout_seconds=_parse_int(
                raw.get("PROXMOX_REQUEST_TIMEOUT_SECONDS"), default=15
            ),
            default_iso_selector=default_iso_selector,
            safety_policy=safety_policy,
        )

    @property
    def is_live_configured(self) -> bool:
        return bool(self.proxmox_url)

    @property
    def effective_username(self) -> str | None:
        if not self.proxmox_user or not self.proxmox_realm:
            return None
        if "@" in self.proxmox_user:
            return self.proxmox_user
        return f"{self.proxmox_user}@{self.proxmox_realm}"

    @property
    def sanitized_host(self) -> str:
        if not self.proxmox_url:
            return "not-configured"
        parsed = urlparse(self.proxmox_url)
        host = parsed.hostname or parsed.netloc or self.proxmox_url
        if parsed.port:
            return f"{host}:{parsed.port}"
        return host

    @property
    def api_url(self) -> str:
        if not self.proxmox_url:
            raise SettingsError("PROXMOX_URL is required for live Proxmox access.")
        return f"{self.proxmox_url}{self.proxmox_api_base}"

    def validate_live_requirements(self) -> None:
        self.validate_live_endpoint_requirements()
        missing: list[str] = []
        if not self.proxmox_user:
            missing.append("PROXMOX_USER")
        if not self.proxmox_password:
            missing.append("PROXMOX_PASSWORD")
        if not self.proxmox_realm:
            missing.append("PROXMOX_REALM")
        if missing:
            joined = ", ".join(missing)
            raise SettingsError(f"Missing live Proxmox configuration: {joined}.")

    def validate_live_endpoint_requirements(self) -> None:
        if not self.proxmox_url:
            raise SettingsError("Missing live Proxmox configuration: PROXMOX_URL.")
