from __future__ import annotations

from functools import cached_property
from pathlib import Path
from typing import Literal, overload
from uuid import UUID

from pydantic import BaseModel

from .ty import JSONableDict
from .util import APP_NAME, get_app_dir


def global_config_dir() -> Path:
    return Path(get_app_dir(APP_NAME))


class RootConfig:
    config_dir: Path

    def __init__(self, *, config_dir: Path) -> None:
        self.config_dir = config_dir

    @cached_property
    def secrets_path(self) -> Path:
        return self.config_dir / "secrets.json"

    @cached_property
    def saved_settings_path(self) -> Path:
        return self.config_dir / "saved-defaults.json"


class SavedSettings(BaseModel):
    broker_host: str | None = None
    project: UUID | None = None
    task: UUID | None = None
    action: UUID | None = None
    agent: UUID | None = None
    pam_host: str | None = None
    broker_id: UUID | None = None
    recipes_file: str | None = None

    @classmethod
    def blank(cls) -> SavedSettings:
        return cls(
            broker_host=None,
            project=None,
            task=None,
            action=None,
            agent=None,
            pam_host=None,
            broker_id=None,
            recipes_file=None,
        )

    @classmethod
    def from_file(cls, path: Path, must_exist: bool = False) -> SavedSettings:
        try:
            return cls.model_validate_json(path.read_text())
        except FileNotFoundError:
            if not must_exist:
                return cls.blank()
            else:
                raise

    def reset_defaults(self) -> None:
        """Reset defaut project/task/action/agent, e.g. on host changes."""
        self.project = None
        self.task = None
        self.action = None
        self.agent = None

    def to_json(self) -> JSONableDict:
        data = {}
        for key, value in self.model_dump().items():
            # UUIDs are not JSON-seriazliable by default
            data[key] = str(value) if isinstance(value, UUID) else value
        return data

    def save(
        self,
        path: Path,
    ) -> None:
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(self.model_dump_json(), encoding="utf-8")

    @overload
    def update(
        self,
        field: Literal["broker_host", "pam_host", "recipes_file"],
        value: str | None = None,
    ) -> str | None: ...

    @overload
    def update(
        self,
        field: Literal["project", "task", "action", "agent", "broker_id"],
        value: UUID | None = None,
    ) -> UUID | None: ...

    def update(
        self,
        field: Literal[
            "broker_host",
            "pam_host",
            "project",
            "task",
            "action",
            "agent",
            "broker_id",
            "recipes_file",
        ],
        value: str | UUID | None = None,
    ) -> str | UUID | None:
        old_value = getattr(self, field)
        setattr(self, field, value)
        if old_value != value and field in ["broker_host", "pam_host"]:
            self.reset_defaults()
        return getattr(self, field)
