"""Configuration manager for reading and writing TOML config files."""

import sys
from pathlib import Path

if sys.version_info >= (3, 11):
    import tomllib
else:
    import tomli as tomllib  # type: ignore[import-not-found,unused-ignore]

import tomlkit
from pydantic import ValidationError as PydanticValidationError

from teams_phone.constants import DEFAULT_CONFIG_PATH
from teams_phone.exceptions import ConfigurationError, NotFoundError, ValidationError
from teams_phone.models import Config, TenantProfile


class ConfigManager:
    """Manages Teams Phone CLI configuration file operations.

    Provides methods to load and save configuration from/to TOML files,
    with validation through Pydantic models and format preservation
    when writing.

    Attributes:
        config_path: Path to the configuration file.
    """

    def __init__(self, config_path: str | None = None) -> None:
        """Initialize the ConfigManager.

        Args:
            config_path: Path to the configuration file. Defaults to
                ~/.teams-phone/config.toml if not specified.
        """
        path_str = config_path if config_path is not None else DEFAULT_CONFIG_PATH
        self.config_path: Path = Path(path_str).expanduser()

    def ensure_config_dir(self) -> None:
        """Create the configuration directory if it doesn't exist.

        Creates all parent directories as needed.

        Raises:
            ConfigurationError: If directory creation fails due to permission error.
        """
        try:
            self.config_path.parent.mkdir(parents=True, exist_ok=True)
        except PermissionError as e:
            raise ConfigurationError(
                f"Cannot create configuration directory {self.config_path.parent}: {e}",
                remediation=(
                    f"Check that you have write permissions to {self.config_path.parent}.\n"
                    "You may need to create the directory manually or run with elevated privileges."
                ),
            ) from e

    def load_config(self) -> Config:
        """Load and validate configuration from the TOML file.

        Returns:
            Config: Validated configuration object.

        Raises:
            ConfigurationError: If the file exists but contains invalid
                TOML syntax or fails Pydantic validation.

        Note:
            Returns a default Config instance if the file doesn't exist.
        """
        if not self.config_path.exists():
            return Config()

        try:
            content = self.config_path.read_text(encoding="utf-8")
            data = tomllib.loads(content)
            return Config.model_validate(data)
        except tomllib.TOMLDecodeError as e:
            raise ConfigurationError(
                f"Invalid TOML syntax in {self.config_path}: {e}",
                remediation=(
                    f"Check {self.config_path} for syntax errors.\n"
                    "Ensure all strings are properly quoted and "
                    "section headers use [brackets]."
                ),
            ) from e
        except PydanticValidationError as e:
            raise ConfigurationError(
                f"Invalid configuration in {self.config_path}: {e}",
                remediation=(
                    f"Review the configuration values in {self.config_path}.\n"
                    "Ensure all required fields are present and have valid types."
                ),
            ) from e

    def save_config(self, config: Config) -> None:
        """Save configuration to the TOML file.

        Uses tomlkit to serialize the configuration, which preserves
        formatting when possible. Creates the configuration directory
        if it doesn't exist.

        Args:
            config: Configuration object to save.

        Note:
            This method overwrites the existing file. To preserve
            comments in an existing file, load it with tomlkit first,
            update values, and write back.
        """
        self.ensure_config_dir()

        # Serialize with mode="json" to convert UUID/Enum to strings
        # exclude_none=True because tomlkit doesn't handle None values
        data = config.model_dump(mode="json", exclude_none=True)
        content = tomlkit.dumps(data)
        self.config_path.write_text(content, encoding="utf-8")

    def get_tenant(self, name: str) -> TenantProfile:
        """Retrieve a tenant profile by name.

        Args:
            name: The tenant profile name to look up.

        Returns:
            TenantProfile: The tenant profile configuration.

        Raises:
            NotFoundError: If no tenant with the given name exists.
        """
        config = self.load_config()
        if name not in config.tenants:
            raise NotFoundError(
                f"Tenant '{name}' not found",
                remediation="Run 'teams-phone tenants list' to see configured tenants.",
            )
        return config.tenants[name]

    def set_tenant(self, name: str, profile: TenantProfile) -> None:
        """Add or update a tenant profile.

        Args:
            name: The key name for the tenant profile.
            profile: The tenant profile configuration to save.

        Raises:
            ValidationError: If profile.name does not match the name parameter.
        """
        if profile.name != name:
            raise ValidationError(
                f"Tenant profile name '{profile.name}' does not match key '{name}'",
                remediation="Ensure the profile name matches the key being set.",
            )
        config = self.load_config()
        config.tenants[name] = profile
        self.save_config(config)

    def remove_tenant(self, name: str) -> None:
        """Remove a tenant profile by name.

        Args:
            name: The tenant profile name to remove.

        Raises:
            NotFoundError: If no tenant with the given name exists.

        Note:
            If the removed tenant is the default tenant, the default_tenant
            setting is cleared.
        """
        config = self.load_config()
        if name not in config.tenants:
            raise NotFoundError(
                f"Tenant '{name}' not found",
                remediation="Run 'teams-phone tenants list' to see configured tenants.",
            )
        del config.tenants[name]
        if config.default_tenant == name:
            config.default_tenant = None
        self.save_config(config)

    def get_default_tenant(self) -> str | None:
        """Retrieve the default tenant name.

        Returns:
            The name of the default tenant, or None if not set.
        """
        config = self.load_config()
        return config.default_tenant

    def set_default_tenant(self, name: str) -> None:
        """Set the default tenant.

        Args:
            name: The tenant name to set as default.

        Raises:
            NotFoundError: If the specified tenant doesn't exist.
        """
        config = self.load_config()
        if name not in config.tenants:
            raise NotFoundError(
                f"Tenant '{name}' not found",
                remediation="Run 'teams-phone tenants list' to see configured tenants.",
            )
        config.default_tenant = name
        self.save_config(config)
