"""Configuration management for archivepodcast."""

import json
from datetime import UTC, datetime
from pathlib import Path
from typing import Literal, Self

from pydantic import BaseModel, HttpUrl, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict

from .utils.logger import LoggingConf, get_logger

# Logging should be all done at INFO level or higher as the log level hasn't been set yet
# Modules should all setup logging like this so the log messages include the modules name.
logger = get_logger(__name__)


class AppWebPageConfig(BaseModel):
    """App Web Page Config Object."""

    title: str = "Podcast Archive"
    description: str = (
        "Podcast archive, generated by archivepodcast, available at https://github.com/kism/archivepodcast"
    )
    contact: str = "archivepodcast@localhost"


class AppS3Config(BaseModel):
    """App S3 Config Object."""

    cdn_domain: HttpUrl = HttpUrl("https://example.com/public_url_of_s3_bucket/")
    api_url: HttpUrl | None = None
    bucket: str = ""
    region: str = ""
    access_key_id: str = ""
    secret_access_key: str = ""

    @field_validator("api_url", mode="before")
    def validate_api_url(cls, v: str) -> str | None:  # noqa: N805
        """Validate api_url to allow empty string to be None."""
        if v == "":
            return None

        return v


class AppConfig(BaseModel):
    """App Config Object."""

    web_page: AppWebPageConfig = AppWebPageConfig()
    inet_path: HttpUrl = HttpUrl("http://localhost:5100/")
    storage_backend: Literal["local", "s3"] = "local"
    s3: AppS3Config = AppS3Config()


class PodcastConfig(BaseModel):
    """Podcast Config Object."""

    url: HttpUrl = HttpUrl("https://example.com/podcast/feed.xml")
    new_name: str = ""
    name_one_word: str = ""
    description: str = ""
    live: bool = True
    contact_email: str = ""


class FlaskConfig(BaseModel):
    """Flask Config Object."""

    TESTING: bool = False


class ArchivePodcastConfig(BaseSettings):
    """Config Object."""

    model_config = SettingsConfigDict(
        extra="ignore",
        env_file=".env",
        env_prefix="AP_",
        case_sensitive=False,
        env_file_encoding="utf-8",
        env_nested_delimiter="__",
    )

    app: AppConfig = AppConfig()
    podcasts: list[PodcastConfig] = [PodcastConfig()]
    logging: LoggingConf = LoggingConf()
    flask: FlaskConfig = FlaskConfig()

    def write_config(self, config_path: Path) -> None:
        """Write the current settings to a JSON file."""
        config_path.parent.mkdir(parents=True, exist_ok=True)

        config_data = json.loads(self.model_dump_json())

        if not config_path.exists():
            logger.warning("Writing fresh config file at %s", config_path.absolute())
            config_path.touch()
            existing_data = config_data
        else:
            with config_path.open("r") as f:
                existing_data = json.load(f)

        new_file_content_str = json.dumps(config_data)

        if existing_data != config_data:  # The new object will be valid, so we back up the old one
            time_str = datetime.now(tz=UTC).strftime("%Y-%m-%d_%H%M%S")
            config_backup_dir = config_path.parent / "config_backups"
            config_backup_dir.mkdir(parents=True, exist_ok=True)
            backup_file = config_backup_dir / f"{config_path.stem}_{time_str}{config_path.suffix}.bak"
            logger.warning(
                "Validation has changed the config file, backing up the old one to %s",
                backup_file,
            )
            with backup_file.open("w") as f:
                f.write(json.dumps(existing_data))

        with config_path.open("w") as f:
            f.write(new_file_content_str)

        config_path_json = config_path.with_suffix(".json")
        logger.info("Writing config to %s", config_path_json)
        with config_path_json.open("w") as f:
            f.write(self.model_dump_json(indent=2, exclude_none=False))

        logger.info("Config write complete")

    @classmethod
    def force_load_config_file(cls, config_path: Path) -> Self:
        """Load the configuration file. File contents takes precedence over env vars."""
        if not config_path.exists():
            logger.warning(
                "Config file %s does not exist, loading defaults",
                config_path.absolute(),
            )
            return cls()

        logger.info("Loading config from %s", config_path.absolute())
        with config_path.open("r") as f:
            config = json.load(f)

        return cls(**config)

    def post_validate(self) -> None:
        """Post validation function."""
        for i, podcast in enumerate(self.podcasts):
            if podcast.name_one_word == "":
                msg = f"Please fill in the podcast details on entry {i}\n"
                msg += podcast.model_dump_json()
                raise ValueError(msg)
