"""Worker task: sync project env vars to all runtime targets.

Targets:
  1. CF Pages project — injects vars into the GitHub Actions build environment
     (frontend/BFF layer) as CF Pages secrets or plain env vars.
  2. P2HS Cloud control plane — Cloud stores them in D1 and injects them into
     edge function / queue consumer Worker bindings at dispatch time.

Triggered after any create / update / delete on project_env_vars, and as
a pre-step in the project deploy pipeline.
"""
from __future__ import annotations

import asyncio

import structlog

logger = structlog.get_logger()


def _with_platform_env_vars(
    entries: list[dict],
    *,
    project_id: str,
    client_id: str | None,
    service_token: str | None = None,
) -> list[dict]:
    """Add Core-owned platform vars required by generated app API clients."""
    from src.config import get_settings

    settings = get_settings()
    cloud_public_url = (
        settings.cloud_public_api_url or settings.cloud_control_api_url or ""
    ).rstrip("/")
    core_public_url = (settings.api_url or "").rstrip("/")
    platform_entries = [
        ("PROJECT_ID", project_id, "public"),
        ("NUXT_PUBLIC_P2HS_PROJECT_ID", project_id, "public"),
        ("VITE_P2HS_PROJECT_ID", project_id, "public"),
    ]
    if client_id:
        platform_entries.extend(
            [
                ("NUXT_PUBLIC_P2HS_CLIENT_ID", client_id, "public"),
                ("VITE_P2HS_CLIENT_ID", client_id, "public"),
            ]
        )
    if cloud_public_url:
        # Server-only. Never inject public Vite/Nuxt keys — those embed in the
        # browser bundle. Generated apps call same-origin `/api/*`.
        platform_entries.extend(
            [
                ("CLOUD_GATEWAY_URL", cloud_public_url, "secret"),
            ]
        )
    if service_token:
        platform_entries.append(("CLOUD_SERVICE_TOKEN", service_token, "secret"))
    generated_site_key = (
        getattr(settings, "generated_app_turnstile_site_key", "") or ""
    ).strip()
    generated_secret = (
        getattr(settings, "generated_app_turnstile_secret", "") or ""
    ).strip()
    if generated_site_key:
        platform_entries.extend(
            [
                ("TURNSTILE_SITE_KEY", generated_site_key, "public"),
                ("NUXT_PUBLIC_TURNSTILE_SITE_KEY", generated_site_key, "public"),
                ("VITE_TURNSTILE_SITE_KEY", generated_site_key, "public"),
            ]
        )
    if generated_secret:
        platform_entries.append(("TURNSTILE_SECRET", generated_secret, "secret"))
    if core_public_url:
        # Server-only. Browser bundles must never see a Core origin.
        platform_entries.append(("P2HS_CORE_URL", core_public_url, "secret"))

    platform_keys = {key for key, _, _ in platform_entries}
    out = [
        dict(e)
        for e in entries
        if not (str(e.get("key")) in platform_keys and str(e.get("environment", "all")) == "all")
    ]
    seen = {(str(e.get("key")), str(e.get("environment", "all"))) for e in out}
    for key, value, visibility in platform_entries:
        marker = (key, "all")
        if marker in seen:
            continue
        out.append(
            {
                "key": key,
                "value": value,
                "visibility": visibility,
                "environment": "all",
            }
        )
        seen.add(marker)
    return out


async def sync_project_env_vars(ctx, *, project_id: str) -> None:
    """Decrypt all project_env_vars for *project_id* and push to CF Pages + Cloud."""
    from uuid import UUID

    from src.config import get_settings
    from src.core.database import async_session_factory
    from src.core.security.encryption import decrypt_value
    from src.domain.projects.repository import ProjectEnvVarRepository, ProjectRepository
    from src.integrations.cf_env_adapter import CloudflareEnvVarAdapter
    from src.integrations.cloud_control_client import get_cloud_client_for_workspace

    db_factory = ctx.get("db_factory", async_session_factory)
    pid = UUID(project_id)

    async with db_factory() as db:
        project = await ProjectRepository(db).get_by_id(pid)
        if not project:
            logger.warning("env_sync_project_not_found", project_id=project_id)
            return

        rows = await ProjectEnvVarRepository(db).list_all_for_deploy(pid)
        cloud_client = await get_cloud_client_for_workspace(project.workspace_id, db)
        from src.domain.edge_functions.repository import ProjectFunctionTokenRepository
        from src.domain.edge_functions.runtime_token import ensure_project_cloud_service_token

        service_token = await ensure_project_cloud_service_token(
            ProjectFunctionTokenRepository(db),
            cloud_client,
            pid,
            always_push=True,
        )
        from src.domain.deployments.repository import DeployRepository

        deploy_history = await DeployRepository(db).list_for_project(pid)
        has_successful_pages_deployment = any(
            d.status == "live"
            and (d.hosting_provider or "").strip().lower() in {"cloudflare_pages", "cloudflare"}
            for d in deploy_history
        )
        await db.commit()

    entries = [
        {
            "key": r.key,
            "value": decrypt_value(r.encrypted_value),
            "visibility": r.visibility,
            "environment": r.environment,
        }
        for r in rows
    ]
    client_id = f"cli_{project.project_ref or project_id}"
    entries = _with_platform_env_vars(
        entries,
        project_id=project_id,
        client_id=client_id,
        service_token=service_token,
    )

    # Push to both targets concurrently. CF Pages needs project_ref; Cloud uses project_id.
    tasks = []

    from src.domain.hosting.deployment_targets import is_w4p_project

    if project.project_ref and not is_w4p_project(
        project.type,
        project.created_at,
        get_settings(),
        primary_hosting_provider=getattr(project, "primary_hosting_provider", None),
        assume_unassigned_w4p=not has_successful_pages_deployment,
        has_successful_pages_deployment=has_successful_pages_deployment,
    ):
        adapter = CloudflareEnvVarAdapter()
        tasks.append(_push_to_pages(adapter, project.project_ref, entries, project_id))
    elif project.project_ref:
        logger.info("env_sync_pages_skipped_w4p_static", project_id=project_id)
        tasks.append(_push_w4p_runtime_secrets(project, project_id, entries))
    else:
        logger.warning("env_sync_pages_skipped_no_ref", project_id=project_id)

    tasks.append(_push_to_cloud(cloud_client, project_id, entries))

    results = await asyncio.gather(*tasks, return_exceptions=True)
    for result in results:
        if isinstance(result, Exception):
            # Log but re-raise the first error so arq retries the whole task
            logger.error("env_sync_target_failed", project_id=project_id, error=str(result))
            raise result

    logger.info("env_sync_ok", project_id=project_id, count=len(entries))


async def _push_to_pages(adapter, project_ref: str, entries: list[dict], project_id: str) -> None:
    try:
        await adapter.sync_to_hosting(project_ref, entries)
        logger.info("env_sync_pages_ok", project_id=project_id)
    except Exception as exc:
        logger.error("env_sync_pages_failed", project_id=project_id, error=str(exc))
        raise
    try:
        retried = await adapter.retry_runtime_deployments(project_ref)
        if retried:
            logger.info("env_sync_pages_redeployed", project_id=project_id, count=len(retried))
    except Exception as exc:
        logger.warning("env_sync_pages_retry_skipped", project_id=project_id, error=str(exc))


async def _push_w4p_runtime_secrets(project, project_id: str, entries: list[dict]) -> None:
    """Refresh Worker secrets on hostnames already cut over to the project runtime.

    Plaintext upload metadata is never used. Hostnames still on the Cloud proxy are left alone.
    """
    import json

    from src.domain.hosting.factory import get_w4p_static_provider
    from src.domain.hosting.hostnames import canonical_live_url, canonical_preview_url
    from src.config import get_settings
    from urllib.parse import urlparse

    settings = get_settings()
    values = {str(item.get("key")): str(item.get("value") or "") for item in entries}
    secrets = {
        "PROJECT_ID": project_id,
        "CLOUD_GATEWAY_URL": values.get("CLOUD_GATEWAY_URL", ""),
    }
    if not secrets["CLOUD_GATEWAY_URL"]:
        return
    for environment, url_for in (("production", canonical_live_url), ("preview", canonical_preview_url)):
        provider = get_w4p_static_provider(settings, environment=environment)
        hostname = urlparse(url_for(project.project_ref, settings.hosting_public_apex)).netloc
        raw = await provider._kv_read(f"host:{hostname}")
        if not raw:
            continue
        try:
            record = json.loads(raw)
        except json.JSONDecodeError:
            continue
        if record.get("api_execution") != "project_runtime" or not record.get("script_name"):
            continue
        await provider.bind_runtime_secrets(str(record["script_name"]), secrets)
        logger.info("env_sync_w4p_secrets_ok", project_id=project_id, environment=environment)


async def _push_to_cloud(cloud_client, project_id: str, entries: list[dict]) -> None:
    try:
        await cloud_client.upsert_env_vars(project_id, entries)
        logger.info("env_sync_cloud_ok", project_id=project_id)
    except Exception as exc:
        logger.error("env_sync_cloud_failed", project_id=project_id, error=str(exc))
        raise
