import logging
from enum import StrEnum
from pathlib import Path
from typing import Any, Self, final

from pydantic import BaseModel, ConfigDict, Field, model_validator
from pydantic.root_model import RootModel
from pydantic_core import PydanticUndefined

from .file_operations import load_yaml_file
from .tuxedo_detection import is_tuxedo_device, is_tuxedo_os

logger = logging.getLogger(__name__)


class ConfigError(Exception):
    """Custom exception for configuration errors."""

    def __init__(self, filename: Path, cause: str) -> None:
        """Initialize a new ConfigError.

        Args:
        filename: The path of the config file.
        cause: Message indicating the cause.

        """
        cause = f"While processing file at '{filename}': {cause}"
        super().__init__(cause)


class ConfigEntryType(StrEnum):
    PKG = "package"
    SCRIPT = "script"


class RecursiveConfigEntry(BaseModel):
    pci: list[str] | None = Field(alias="PCI", default=None)
    usb: list[str] | None = Field(alias="USB", default=None)
    gpu_count_min: int | None = Field(alias="GPU_COUNT_min", default=None)
    any: list["RecursiveConfigEntry"] = Field(default_factory=list)
    not_: list["RecursiveConfigEntry"] = Field(alias="not", default_factory=list)


@final
class ConfigEntry(RecursiveConfigEntry):  # pyright: ignore[reportUninitializedInstanceVariable]
    type: ConfigEntryType = Field(alias="type")


@final
class Config(RootModel[dict[str, ConfigEntry]]):  # pyright: ignore[reportUninitializedInstanceVariable]
    model_config = ConfigDict(strict=True)

    @model_validator(mode="before")
    @classmethod
    def allow_empty_input(cls, data: Any) -> Any:  # pyright: ignore[reportExplicitAny]  # noqa: ANN401
        # Allow empty input
        if data is PydanticUndefined:
            return {}
        return data

    def update(self, config: Self) -> None:
        self.root.update(config.root)


def get_config() -> Config:
    """
    Loads and merges all configuration layers in correct priority order.
    Later files fully override earlier ones.
    """

    base_path = Path("/usr/lib/tuxedo-tomte")

    files = ["base.yaml"]

    if is_tuxedo_device():
        files.append("tuxedo-devices.yaml")
    if is_tuxedo_os():
        files.append("tuxedo-os.yaml")

    logger.info("Loading TUXEDO Tomte configuration layers...")

    loaded: list[Config] = []

    for fname in files:
        full = base_path / fname
        logger.debug(f"Processing: {full}")

        data = load_yaml_file(full)
        config = Config(**data)
        loaded.append(config)

    # Later configs override earlier ones entirely.
    config = loaded.pop(0)
    for cfg in loaded:
        config.update(cfg)

    logger.debug("Final merged configuration:")
    logger.debug(loaded)

    return config
