"""Task: bootstrap a new project into the P2HS Cloud control plane.

Called after project creation.  Uses the bootstrap endpoint which atomically
upserts workspace + project + default client + empty capabilities in one call.

All control endpoints are idempotent (INSERT OR REPLACE), so retries are safe.
"""
from __future__ import annotations

from urllib.parse import urlsplit
from uuid import UUID

import structlog

logger = structlog.get_logger()


def _origin_of(url_or_host: str) -> str | None:
    raw = (url_or_host or "").strip()
    if not raw:
        return None
    if "://" not in raw:
        raw = f"https://{raw}"
    parsed = urlsplit(raw)
    if parsed.scheme not in {"http", "https"} or not parsed.hostname:
        return None
    return f"{parsed.scheme}://{parsed.netloc}"


def _project_app_origins(project) -> list[str]:
    origins: list[str] = []
    ref = getattr(project, "project_ref", None)
    try:
        from src.config import get_settings
        from src.domain.hosting.hostnames import canonical_live_url, canonical_preview_url

        apex = (get_settings().hosting_public_apex or "p2hsapp.com").strip()
        apexes = {apex, "p2hsapp.com"}
        if ref:
            for item in apexes:
                origins.append(_origin_of(canonical_preview_url(ref, item)))
                origins.append(_origin_of(canonical_live_url(ref, item)))
            origins.append(f"https://preview.p2hs-{ref}.pages.dev")
    except Exception:
        logger.warning("cloud_sync_origins_skipped", project_id=str(getattr(project, "id", "")))
    for host in (getattr(project, "preview_hostname", None), getattr(project, "live_hostname", None)):
        origins.append(_origin_of(host or ""))
    seen: set[str] = set()
    unique: list[str] = []
    for origin in origins:
        if origin and origin not in seen:
            seen.add(origin)
            unique.append(origin)
    return unique


async def sync_project_to_cloud(ctx: dict, project_id: str) -> dict:
    from src.core.database import async_session_factory
    from src.domain.cloud.service import DEFAULT_CLOUD_REQUEST_TYPES
    from src.domain.connectors.repository import ConnectorRepository
    from src.domain.deployments.repository import DomainRepository
    from src.domain.projects.repository import ProjectRepository
    from src.domain.workspaces.repository import WorkspaceRepository
    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("cloud_sync_skip", reason="project_not_found", project_id=project_id)
            return {"status": "skipped", "reason": "project_not_found"}

        workspace = await WorkspaceRepository(db).get_by_id(project.workspace_id)
        if not workspace:
            logger.warning("cloud_sync_skip", reason="workspace_not_found", project_id=project_id)
            return {"status": "skipped", "reason": "workspace_not_found"}
        connector_repo = ConnectorRepository(db)
        connector_payloads = []
        for connection in await connector_repo.list_connections_for_project(pid):
            connector = await connector_repo.get_connector_by_id(connection.connector_id)
            if not connector:
                continue
            permissions = await connector_repo.list_permissions(connection.id)
            connector_payloads.append(
                {
                    "connection_id": str(connection.id),
                    "connector_key": connector.key,
                    "workspace_id": str(connection.workspace_id),
                    "project_id": str(connection.project_id) if connection.project_id else None,
                    "name": connection.name,
                    "status": connection.status,
                    "auth_data_ref": connection.auth_data_ref,
                    "scopes": list(connection.scopes or []),
                    "config": connection.config_json or {},
                    "permissions": [
                        {
                            "connection_id": str(permission.connection_id),
                            "project_id": str(connection.project_id) if connection.project_id else None,
                            "action_slug": permission.action_slug,
                            "mode": permission.mode,
                            "policy": permission.policy_json or {},
                        }
                        for permission in permissions
                    ],
                }
            )

        cloud_client = await get_cloud_client_for_workspace(project.workspace_id, db)

        ws_id = str(workspace.id)
        proj_id = str(project.id)
        client_id = f"cli_{project.project_ref or proj_id}"

        workspace_payload = {
            "workspace_id": ws_id,
            "tenant_id": workspace.tenant_ref,
            "name": workspace.name,
            "status": "active" if not workspace.deleted_at else "archived",
            "plan_slug": workspace.selected_plan_slug,
            "cloud_enabled": True,
            "suspended": False,
            "metadata": {},
        }

        origins = _project_app_origins(project)
        for row in await DomainRepository(db).list_for_project(pid):
            if row.verified_at:
                origin = _origin_of(row.domain)
                if origin and origin not in origins:
                    origins.append(origin)

        project_payload = {
            "project_id": proj_id,
            "workspace_id": ws_id,
            "name": project.name,
            "status": project.status,
            "environment": None,
            "cloud_enabled": True,
            "suspended": False,
            "allowed_request_types": list(DEFAULT_CLOUD_REQUEST_TYPES),
            "origins": origins,
            "metadata": {"project_ref": project.project_ref} if project.project_ref else {},
        }

        # For shopify_app projects, the CF Worker URL is the allowed origin for Cloud handshake.
        # The URL is only known after the first wrangler deploy; cloud_sync re-runs after it resolves.
        client_allowed_origins: list[str] = list(origins)
        if project.type == "shopify_app":
            from src.domain.shopify_apps.repository import ShopifyAppConfigRepository
            shopify_cfg = await ShopifyAppConfigRepository(db).get(pid)
            if shopify_cfg and shopify_cfg.worker_url:
                worker_origin = _origin_of(shopify_cfg.worker_url)
                if worker_origin and worker_origin not in client_allowed_origins:
                    client_allowed_origins.append(worker_origin)

        client_payload = {
            "client_id": client_id,
            "workspace_id": ws_id,
            "project_id": proj_id,
            "name": f"{project.name} Client",
            "status": "active",
            "allowed_origins": client_allowed_origins,
            "allowed_capabilities": [],
            "handshake_mode": "token",
        }

        capabilities_payload = {
            "project_id": proj_id,
            "version": 1,
            "capabilities": {
                "query_dsl_v1": True,
                "bulk_import": True,
                "integrations.execute": True,
                "livechat.enabled": False,
                "voice.enabled": False,
            },
        }

        await cloud_client.bootstrap_project(
            workspace=workspace_payload,
            project=project_payload,
            clients=[client_payload],
            capabilities=capabilities_payload,
            integrations=[],
            connectors=connector_payloads,
        )

        # Auth is part of the same control-plane contract as project origins
        # and capabilities.  Reconcile it on every idempotent cloud sync so a
        # member added after initial provisioning, or a stale Cloud role, cannot
        # authenticate successfully and then fail the protected staff dashboard.
        from src.domain.project_auth.repository import (
            ProjectAuthConfigRepository,
            ProjectAuthKeyRepository,
            ProjectAuthRoleRepository,
        )
        from src.domain.project_auth.service import ProjectAuthService

        auth_service = ProjectAuthService(
            config_repo=ProjectAuthConfigRepository(db),
            key_repo=ProjectAuthKeyRepository(db),
            role_repo=ProjectAuthRoleRepository(db),
            cloud_client=cloud_client,
        )
        await auth_service.ensure_staff_roles_and_owner_admin(pid)
        await db.commit()
        logger.info("cloud_sync_auth_reconciled", project_id=proj_id)

    from src.workers.enqueue import enqueue_env_sync

    await enqueue_env_sync(proj_id)

    async with db_factory() as db:
        from src.domain.cloud_infra.repository import (
            CFAccountRepository,
            CloudInstanceRepository,
            CloudShardRepository,
            ProjectCloudAssignmentRepository,
            WorkspaceCloudAssignmentRepository,
        )
        from src.domain.cloud_infra.service import CloudInfraService
        from src.core.exceptions import NotFoundError

        try:
            svc = CloudInfraService(
                cf_account_repo=CFAccountRepository(db),
                instance_repo=CloudInstanceRepository(db),
                assignment_repo=WorkspaceCloudAssignmentRepository(db),
                shard_repo=CloudShardRepository(db),
                project_shard_repo=ProjectCloudAssignmentRepository(db),
            )
            assignment = await svc.auto_assign_project_shard(pid)
            await db.commit()
            logger.info(
                "project_shard_assigned",
                project_id=proj_id,
                shard_key=assignment.shard.shard_key if assignment.shard else None,
            )
        except NotFoundError as exc:
            logger.warning(
                "project_shard_assign_skipped",
                project_id=proj_id,
                reason=str(exc),
            )

    return {"status": "synced", "project_id": proj_id, "client_id": client_id}
