from typing import Self
from zoneinfo import ZoneInfo

import yaml
from loguru import logger
from pydantic import EmailStr, Field, PositiveInt, ValidationError
from pydantic_settings import BaseSettings

from reserve_it.models.field_types import CustomFormField, YamlPath


class AppConfig(BaseSettings):
    """App configuration model passed to `build_app()`, either loaded from
    `app-config.yaml` or constructed at runtime.

    Args:
        title (str): Title of your app, displayed on the home page if there are multiple
            resources, and openapi docs if enabled.
        description (str): Description of your app, displayed on the home page if there
            are multiple resources, and openapi docs if enabled.
        app_email (str): The email address of the dedicated Google account used for
            resource calendars.
        timezone (ZoneInfo): the IANA timezone to use for displaying all calendars.
        version (str): App version number.
        db_echo (bool, optional): Whether resource sqlite databases should echo their
            operations to the console, for debugging purposes. Defaults to False.
        openapi_url (str | None, optional): If desired, a URL for the openapi docs
            generated by FastAPI. Defaults to None, which disables autogeneration of
            docs.
        custom_form_fields (list[CustomFormField], optional): custom html form input
            fields to add for all reservation pages, appended to any fields defined in
            individual resource config yaml files. Defaults to empty list.
        maximum_days_ahead (int | None, optional): Positive integer, global default for
            how many days ahead users can reserve all resources. If None, reservations
            can be made for any time in the future. Individual resource config files can
            override this. Defaults to 14.
        minutes_before_reminder (int, optional): Positive integer, global default for
            how many minutes before a reservation to send an email reminder to the user,
            if they've selected to receive one. Individual resource config files can
            override this. Defaults to 60.
        calendar_shown (bool, optional): If False, omit the embedded Google calendar
            view from all reservation pages. The calendar view will also be omitted if
            the resource has more than 4 calendars, to avoid visual clutter. Individual
            resource config files can override this. Defaults to True.
        contact_email (str, optional): A contact email address for user issues, listed
            on all reservation pages if desired. Individual resource config files can
            override this. Defaults to None.
    """

    title: str
    description: str
    version: str
    app_email: EmailStr
    timezone: ZoneInfo
    db_echo: bool = False
    openapi_url: str | None = None
    custom_form_fields: list[CustomFormField] = Field(default_factory=list)
    # stuff that can be overridden in individual resource-configs
    maximum_days_ahead: PositiveInt | None = 14
    minutes_before_reminder: PositiveInt = 60
    calendar_shown: bool = True
    contact_email: EmailStr | None = None

    @classmethod
    def from_yaml(cls, path: YamlPath) -> Self:
        """helper method to load the config from the yaml path passed.

        Args:
            path (YamlPath): pathlib.Path object for a valid yaml file defining the
                config with the same args listed.
        """
        data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
        return cls._model_validate_cleanly(data)

    @classmethod
    def _model_validate_cleanly(cls, obj: dict, *, context=None, **kwargs):
        """model_validate overload that adds clean error log"""
        try:
            return super().model_validate(obj, context=context, **kwargs)
        except ValidationError as e:
            logger.error(f"Error loading AppConfig: {e}")
            # Kill the process cleanly; uvicorn will just see a non-zero exit
            raise SystemExit(1) from e
