"""Brand configuration for white-label PDF reports.

Loads brand.yaml with company name, logo, colors, and contact info.
Agency tier feature — gated by ``branded_pdf``.
"""

from __future__ import annotations

import re
import sys
from pathlib import Path  # noqa: TC003 — Pydantic needs runtime access
from typing import Annotated

import yaml
from pydantic import (
    BaseModel,
    ConfigDict,
    field_validator,
    model_validator,
)
from pydantic.functional_validators import BeforeValidator


def _coerce_path(v: object) -> Path | None:
    """Coerce string to Path for strict-mode compatibility."""
    if v is None:
        return None
    if isinstance(v, Path):
        return v
    if isinstance(v, str):
        return Path(v)
    msg = f"Expected str or Path, got {type(v).__name__}"
    raise TypeError(msg)


_CoercedPath = Annotated[Path | None, BeforeValidator(_coerce_path)]

_MAX_LOGO_BYTES = 2 * 1024 * 1024  # 2 MB
_VALID_LOGO_EXTENSIONS = {".png", ".jpg", ".jpeg", ".svg"}
_HEX_COLOR_RE = re.compile(r"^#[0-9a-fA-F]{6}$")


class BrandConfig(BaseModel):
    """White-label brand configuration for PDF reports."""

    model_config = ConfigDict(strict=True)

    company_name: str
    logo_path: _CoercedPath = None
    primary_color: str | None = None
    accent_color: str | None = None
    contact_email: str | None = None
    contact_phone: str | None = None
    website: str | None = None
    report_footer: str | None = None
    cover_page_title: str | None = None

    # Set by load_brand_config to resolve relative logo paths
    _brand_dir: Path | None = None

    @field_validator("primary_color", "accent_color")
    @classmethod
    def _validate_hex_color(cls, v: str | None) -> str | None:
        if v is not None and not _HEX_COLOR_RE.match(v):
            msg = f"Invalid hex color '{v}'. Expected format: #RRGGBB"
            raise ValueError(msg)
        return v

    @field_validator("logo_path")
    @classmethod
    def _validate_logo_extension(cls, v: Path | None) -> Path | None:
        if v is not None:
            suffix = v.suffix.lower()
            if suffix not in _VALID_LOGO_EXTENSIONS:
                msg = (
                    f"Unsupported logo format '{suffix}'."
                    f" Allowed: {', '.join(sorted(_VALID_LOGO_EXTENSIONS))}"
                )
                raise ValueError(msg)
        return v

    @model_validator(mode="after")
    def _validate_logo_file(self) -> BrandConfig:
        if self.logo_path is None:
            return self

        logo = self.logo_path

        # Only check existence for absolute paths — relative
        # paths are resolved later in load_brand_config.
        if not logo.is_absolute():
            return self

        if not logo.exists():
            msg = f"Logo file not found: {logo}"
            raise ValueError(msg)

        size = logo.stat().st_size
        if size > _MAX_LOGO_BYTES:
            size_mb = size / (1024 * 1024)
            msg = f"Logo file too large ({size_mb:.1f}MB, max 2MB): {logo}"
            raise ValueError(msg)

        return self


def load_brand_config(brand_path: Path) -> BrandConfig:
    """Load brand configuration from a YAML file.

    Relative ``logo_path`` values are resolved against the directory
    containing *brand_path*.
    """
    print(
        f"Loading brand config from {brand_path}",
        file=sys.stderr,
    )

    raw = brand_path.read_text(encoding="utf-8")
    data = yaml.safe_load(raw)

    if not isinstance(data, dict):
        msg = f"Brand config must be a YAML mapping, got {type(data).__name__}"
        raise ValueError(msg)

    brand_dir = brand_path.resolve().parent

    config = BrandConfig.model_validate(data)
    # Re-validate with brand_dir context for logo resolution
    config._brand_dir = brand_dir

    # If logo_path is relative, resolve now
    if config.logo_path is not None and not config.logo_path.is_absolute():
        resolved = brand_dir / config.logo_path
        if not resolved.exists():
            msg = f"Logo file not found: {resolved}"
            raise ValueError(msg)
        size = resolved.stat().st_size
        if size > _MAX_LOGO_BYTES:
            size_mb = size / (1024 * 1024)
            msg = f"Logo file too large ({size_mb:.1f}MB, max 2MB): {resolved}"
            raise ValueError(msg)
        config.logo_path = resolved

    return config
