# mypy: disable-error-code="arg-type,assignment"
# SQLAlchemy 1.x Column() descriptors are Column[T] statically, T at runtime.
"""
Banner admin service — site config + category + vendor CRUD.

Phase 1 Step 4: extracted from ``compliance.api.banner_routes``.
Covers the admin surface: site configs and the nested category and
vendor collections.
"""

import uuid
from datetime import datetime, timezone
from typing import Any

from sqlalchemy.orm import Session

from compliance.db.banner_models import (
    BannerCategoryConfigDB,
    BannerSiteConfigDB,
    BannerVendorConfigDB,
)
from compliance.domain import ConflictError, NotFoundError, ValidationError
from compliance.schemas.banner import (
    CategoryConfigCreate,
    SiteConfigCreate,
    SiteConfigUpdate,
    VendorConfigCreate,
)
from compliance.services._banner_serializers import (
    category_to_dict,
    site_config_to_dict,
    vendor_to_dict,
)

_UPDATABLE_SITE_FIELDS = (
    "site_name",
    "site_url",
    "banner_title",
    "banner_description",
    "privacy_url",
    "imprint_url",
    "dsb_name",
    "dsb_email",
    "theme",
    "tcf_enabled",
    "is_active",
)


class BannerAdminService:
    """Business logic for the banner admin surface."""

    def __init__(self, db: Session) -> None:
        self.db = db

    # ------------------------------------------------------------------
    # Internal lookups
    # ------------------------------------------------------------------

    def _site_or_raise(self, tenant_id: uuid.UUID, site_id: str) -> BannerSiteConfigDB:
        config = (
            self.db.query(BannerSiteConfigDB)
            .filter(
                BannerSiteConfigDB.tenant_id == tenant_id,
                BannerSiteConfigDB.site_id == site_id,
            )
            .first()
        )
        if not config:
            raise NotFoundError("Site config not found")
        return config

    @staticmethod
    def _parse_uuid(raw: str, label: str) -> uuid.UUID:
        try:
            return uuid.UUID(raw)
        except ValueError as exc:
            raise ValidationError(f"Invalid {label} ID") from exc

    # ------------------------------------------------------------------
    # Site configs
    # ------------------------------------------------------------------

    def list_sites(self, tenant_id: str) -> list[dict[str, Any]]:
        tid = uuid.UUID(tenant_id)
        configs = (
            self.db.query(BannerSiteConfigDB)
            .filter(BannerSiteConfigDB.tenant_id == tid)
            .order_by(BannerSiteConfigDB.created_at.desc())
            .all()
        )
        return [site_config_to_dict(c) for c in configs]

    def create_site(self, tenant_id: str, body: SiteConfigCreate) -> dict[str, Any]:
        tid = uuid.UUID(tenant_id)
        existing = (
            self.db.query(BannerSiteConfigDB)
            .filter(
                BannerSiteConfigDB.tenant_id == tid,
                BannerSiteConfigDB.site_id == body.site_id,
            )
            .first()
        )
        if existing:
            raise ConflictError(
                f"Site config for '{body.site_id}' already exists"
            )
        config = BannerSiteConfigDB(
            tenant_id=tid,
            site_id=body.site_id,
            site_name=body.site_name,
            site_url=body.site_url,
            banner_title=body.banner_title or "Cookie-Einstellungen",
            banner_description=body.banner_description,
            privacy_url=body.privacy_url,
            imprint_url=body.imprint_url,
            dsb_name=body.dsb_name,
            dsb_email=body.dsb_email,
            theme=body.theme or {},
            tcf_enabled=body.tcf_enabled,
        )
        self.db.add(config)
        self.db.commit()
        self.db.refresh(config)
        return site_config_to_dict(config)

    def update_site(
        self, tenant_id: str, site_id: str, body: SiteConfigUpdate
    ) -> dict[str, Any]:
        tid = uuid.UUID(tenant_id)
        config = self._site_or_raise(tid, site_id)
        for field in _UPDATABLE_SITE_FIELDS:
            val = getattr(body, field, None)
            if val is not None:
                setattr(config, field, val)
        config.updated_at = datetime.now(timezone.utc)
        self.db.commit()
        self.db.refresh(config)
        return site_config_to_dict(config)

    def delete_site(self, tenant_id: str, site_id: str) -> None:
        tid = uuid.UUID(tenant_id)
        config = self._site_or_raise(tid, site_id)
        self.db.delete(config)
        self.db.commit()

    # ------------------------------------------------------------------
    # Categories
    # ------------------------------------------------------------------

    def list_categories(self, tenant_id: str, site_id: str) -> list[dict[str, Any]]:
        tid = uuid.UUID(tenant_id)
        config = self._site_or_raise(tid, site_id)
        cats = (
            self.db.query(BannerCategoryConfigDB)
            .filter(BannerCategoryConfigDB.site_config_id == config.id)
            .order_by(BannerCategoryConfigDB.sort_order)
            .all()
        )
        return [category_to_dict(c) for c in cats]

    def create_category(
        self, tenant_id: str, site_id: str, body: CategoryConfigCreate
    ) -> dict[str, Any]:
        tid = uuid.UUID(tenant_id)
        config = self._site_or_raise(tid, site_id)
        cat = BannerCategoryConfigDB(
            site_config_id=config.id,
            category_key=body.category_key,
            name_de=body.name_de,
            name_en=body.name_en,
            description_de=body.description_de,
            description_en=body.description_en,
            is_required=body.is_required,
            sort_order=body.sort_order,
        )
        self.db.add(cat)
        self.db.commit()
        self.db.refresh(cat)
        return category_to_dict(cat)

    def delete_category(self, category_id: str) -> None:
        cid = self._parse_uuid(category_id, "category")
        cat = (
            self.db.query(BannerCategoryConfigDB)
            .filter(BannerCategoryConfigDB.id == cid)
            .first()
        )
        if not cat:
            raise NotFoundError("Category not found")
        self.db.delete(cat)
        self.db.commit()

    # ------------------------------------------------------------------
    # Vendors
    # ------------------------------------------------------------------

    def list_vendors(self, tenant_id: str, site_id: str) -> list[dict[str, Any]]:
        tid = uuid.UUID(tenant_id)
        config = self._site_or_raise(tid, site_id)
        vendors = (
            self.db.query(BannerVendorConfigDB)
            .filter(BannerVendorConfigDB.site_config_id == config.id)
            .all()
        )
        return [vendor_to_dict(v) for v in vendors]

    def create_vendor(
        self, tenant_id: str, site_id: str, body: VendorConfigCreate
    ) -> dict[str, Any]:
        tid = uuid.UUID(tenant_id)
        config = self._site_or_raise(tid, site_id)
        vendor = BannerVendorConfigDB(
            site_config_id=config.id,
            vendor_name=body.vendor_name,
            vendor_url=body.vendor_url,
            category_key=body.category_key,
            description_de=body.description_de,
            description_en=body.description_en,
            cookie_names=body.cookie_names,
            retention_days=body.retention_days,
        )
        self.db.add(vendor)
        self.db.commit()
        self.db.refresh(vendor)
        return vendor_to_dict(vendor)

    def delete_vendor(self, vendor_id: str) -> None:
        vid = self._parse_uuid(vendor_id, "vendor")
        vendor = (
            self.db.query(BannerVendorConfigDB)
            .filter(BannerVendorConfigDB.id == vid)
            .first()
        )
        if not vendor:
            raise NotFoundError("Vendor not found")
        self.db.delete(vendor)
        self.db.commit()
