import uuid

from fastapi import APIRouter, Depends, HTTPException, Request, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from src.db import get_db
from src.extensions.registry import get_registry
from src.models.cookie import CookieCategory
from src.models.iab_gvl import IabGvlMeta
from src.models.org_config import OrgConfig
from src.models.site import Site
from src.models.site_config import SiteConfig
from src.models.site_group_config import SiteGroupConfig
from src.schemas.auth import CurrentUser
from src.schemas.site import SiteConfigResponse
from src.services.config_resolver import (
    CONFIG_FIELDS,
    build_public_config,
    orm_to_config_dict,
    resolve_config,
)
from src.services.dependencies import require_role
from src.services.geoip import detect_region
from src.services.publisher import publish_site_config

router = APIRouter(prefix="/config", tags=["config"])


@router.get("/sites/{site_id}", response_model=SiteConfigResponse)
async def get_public_site_config(
    site_id: uuid.UUID,
    db: AsyncSession = Depends(get_db),
) -> SiteConfig:
    """Public endpoint: retrieve site config for the banner script. No auth required."""
    result = await db.execute(
        select(SiteConfig)
        .join(Site)
        .where(
            SiteConfig.site_id == site_id,
            Site.is_active.is_(True),
            Site.deleted_at.is_(None),
        )
    )
    config = result.scalar_one_or_none()
    if config is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Site configuration not found",
        )
    return config


@router.get("/sites/{site_id}/resolved")
async def get_resolved_config(
    site_id: uuid.UUID,
    region: str | None = None,
    db: AsyncSession = Depends(get_db),
) -> dict:
    """Public endpoint: retrieve fully resolved config with regional overrides applied.

    Applies the full cascade: System → Org → Group → Site → Regional.
    """
    result = await db.execute(
        select(SiteConfig)
        .join(Site)
        .where(
            SiteConfig.site_id == site_id,
            Site.is_active.is_(True),
            Site.deleted_at.is_(None),
        )
    )
    config = result.scalar_one_or_none()
    if config is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Site configuration not found",
        )

    config_dict = orm_to_config_dict(config, include_id=True)

    # Load org defaults via the site
    org_id = await _get_site_org_id(site_id, db)
    org_defaults = await _load_org_defaults(org_id, db) if org_id else None

    # Load site group defaults
    group_id = await _get_site_group_id(site_id, db)
    group_defaults = await _load_group_defaults(group_id, db) if group_id else None

    resolved = resolve_config(
        config_dict,
        org_defaults=org_defaults,
        group_defaults=group_defaults,
        region=region,
    )

    # Set consent_group_id when cross-domain sharing is enabled on the
    # group. The banner uses this as the signal to activate the iframe
    # bridge.
    if group_id and resolved.get("consent_sharing_enabled"):
        resolved["consent_group_id"] = str(group_id)
        if resolved.get("consent_bridge_url"):
            resolved["consent_bridge_url"] = resolved["consent_bridge_url"]

    gvl_version = await _load_gvl_version(db)
    category_tcf_purposes = await _load_category_tcf_purposes(db)
    return build_public_config(
        str(site_id),
        resolved,
        gvl_version=gvl_version,
        category_tcf_purposes=category_tcf_purposes,
    )


@router.get("/sites/{site_id}/geo-resolved")
async def get_geo_resolved_config(
    site_id: uuid.UUID,
    request: Request,
    db: AsyncSession = Depends(get_db),
) -> dict:
    """Public endpoint: resolve config using the visitor's detected region.

    Detects the visitor's region from CDN headers or IP geolocation,
    then applies regional blocking mode overrides automatically.
    Uses the full cascade: System → Org → Group → Site → Regional.
    """
    result = await db.execute(
        select(SiteConfig)
        .join(Site)
        .where(
            SiteConfig.site_id == site_id,
            Site.is_active.is_(True),
            Site.deleted_at.is_(None),
        )
    )
    config = result.scalar_one_or_none()
    if config is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Site configuration not found",
        )

    # Detect region from request
    geo = await detect_region(request)

    config_dict = orm_to_config_dict(config, include_id=True)
    org_id = await _get_site_org_id(site_id, db)
    org_defaults = await _load_org_defaults(org_id, db) if org_id else None
    group_id = await _get_site_group_id(site_id, db)
    group_defaults = await _load_group_defaults(group_id, db) if group_id else None

    resolved = resolve_config(
        config_dict,
        org_defaults=org_defaults,
        group_defaults=group_defaults,
        region=geo.region,
    )
    gvl_version = await _load_gvl_version(db)
    category_tcf_purposes = await _load_category_tcf_purposes(db)
    public = build_public_config(
        str(site_id),
        resolved,
        gvl_version=gvl_version,
        category_tcf_purposes=category_tcf_purposes,
    )

    # Include detected geo info so the banner can use it
    public["detected_country"] = geo.country_code
    public["detected_region"] = geo.region

    return public


@router.get("/geo")
async def get_visitor_geo(request: Request) -> dict:
    """Public endpoint: return the detected region for the current visitor.

    Useful for banner scripts that need to know the region before
    fetching the full config.
    """
    geo = await detect_region(request)
    return {
        "country_code": geo.country_code,
        "region": geo.region,
    }


@router.get("/sites/{site_id}/inheritance")
async def get_config_inheritance(
    site_id: uuid.UUID,
    current_user: CurrentUser = Depends(require_role("owner", "admin", "editor", "viewer")),
    db: AsyncSession = Depends(get_db),
) -> dict:
    """Return the full config inheritance chain for a site.

    Shows the value at each level so the UI can display where each setting
    comes from: system, org, group, or site.
    """
    from src.services.config_resolver import SYSTEM_DEFAULTS

    result = await db.execute(
        select(SiteConfig)
        .join(Site)
        .where(
            SiteConfig.site_id == site_id,
            Site.organisation_id == current_user.organisation_id,
            Site.deleted_at.is_(None),
        )
    )
    config = result.scalar_one_or_none()
    if config is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Site configuration not found",
        )

    site_dict = orm_to_config_dict(config)
    org_defaults = await _load_org_defaults(current_user.organisation_id, db)
    group_id = await _get_site_group_id(site_id, db)
    group_defaults = await _load_group_defaults(group_id, db) if group_id else None

    resolved = resolve_config(
        site_dict,
        org_defaults=org_defaults,
        group_defaults=group_defaults,
    )

    # For each config field, determine the source
    sources: dict[str, dict] = {}
    for field in CONFIG_FIELDS:
        site_val = site_dict.get(field)
        group_val = group_defaults.get(field) if group_defaults else None
        org_val = org_defaults.get(field) if org_defaults else None
        system_val = SYSTEM_DEFAULTS.get(field)

        # Determine effective source (highest priority non-None wins)
        if site_val is not None:
            source = "site"
        elif group_val is not None:
            source = "group"
        elif org_val is not None:
            source = "org"
        elif system_val is not None:
            source = "system"
        else:
            source = "system"

        sources[field] = {
            "resolved_value": resolved.get(field),
            "source": source,
            "site_value": site_val,
            "group_value": group_val,
            "org_value": org_val,
            "system_value": system_val,
        }

    return {
        "site_id": str(site_id),
        "site_group_id": str(group_id) if group_id else None,
        "fields": sources,
    }


@router.post("/sites/{site_id}/publish")
async def publish_config(
    site_id: uuid.UUID,
    current_user: CurrentUser = Depends(require_role("owner", "admin")),
    db: AsyncSession = Depends(get_db),
) -> dict:
    """Publish fully-resolved site config to CDN. Requires admin role."""
    result = await db.execute(
        select(SiteConfig)
        .join(Site)
        .where(
            SiteConfig.site_id == site_id,
            Site.organisation_id == current_user.organisation_id,
            Site.deleted_at.is_(None),
        )
    )
    config = result.scalar_one_or_none()
    if config is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Site configuration not found",
        )

    config_dict = orm_to_config_dict(config, include_id=True)
    org_defaults = await _load_org_defaults(current_user.organisation_id, db)
    group_id = await _get_site_group_id(site_id, db)
    group_defaults = await _load_group_defaults(group_id, db) if group_id else None
    resolved = resolve_config(
        config_dict,
        org_defaults=org_defaults,
        group_defaults=group_defaults,
    )

    # Allow extensions to enrich the published config (e.g. A/B test data)
    registry = get_registry()
    for enricher in registry.config_enrichers:
        await enricher(site_id, db, resolved)

    publish_result = await publish_site_config(str(site_id), resolved)

    if not publish_result.success:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Publish failed: {publish_result.error}",
        )

    return {
        "published": True,
        "path": publish_result.path,
        "published_at": publish_result.published_at,
    }


# ── Helpers ──────────────────────────────────────────────────────────


async def _get_site_org_id(site_id: uuid.UUID, db: AsyncSession) -> uuid.UUID | None:
    """Look up the organisation_id for a site."""
    result = await db.execute(select(Site.organisation_id).where(Site.id == site_id))
    return result.scalar_one_or_none()


async def _get_site_group_id(site_id: uuid.UUID, db: AsyncSession) -> uuid.UUID | None:
    """Look up the site_group_id for a site."""
    result = await db.execute(select(Site.site_group_id).where(Site.id == site_id))
    return result.scalar_one_or_none()


async def _load_org_defaults(organisation_id: uuid.UUID, db: AsyncSession) -> dict | None:
    """Load the org-level config defaults, or None if not set."""
    result = await db.execute(select(OrgConfig).where(OrgConfig.organisation_id == organisation_id))
    org_config = result.scalar_one_or_none()
    if org_config is None:
        return None
    return orm_to_config_dict(org_config)


async def _load_group_defaults(group_id: uuid.UUID, db: AsyncSession) -> dict | None:
    """Load the site-group-level config defaults, or None if not set."""
    result = await db.execute(
        select(SiteGroupConfig).where(SiteGroupConfig.site_group_id == group_id)
    )
    group_config = result.scalar_one_or_none()
    if group_config is None:
        return None
    return orm_to_config_dict(group_config)


async def _load_gvl_version(db: AsyncSession) -> int | None:
    """Return the currently-cached IAB GVL version, or ``None`` if unsynced.

    Surfaced into the public config so the banner can stamp it onto
    generated TC strings (``vendorListVersion`` field).
    """
    result = await db.execute(select(IabGvlMeta.vendor_list_version).limit(1))
    return result.scalar_one_or_none()


async def _load_category_tcf_purposes(db: AsyncSession) -> dict[str, list[int]]:
    """Return the cookie-category slug → TCF purpose IDs mapping.

    The banner uses this to translate accepted cookie categories into
    TCF purpose IDs when building TCData. Categories without a mapping
    are omitted from the result.
    """
    result = await db.execute(
        select(CookieCategory.slug, CookieCategory.tcf_purpose_ids).where(
            CookieCategory.tcf_purpose_ids.isnot(None)
        )
    )
    return {slug: ids for slug, ids in result.all() if ids}
