"""Projects domain - FastAPI router."""
from typing import Optional
from uuid import UUID

from fastapi import APIRouter, Depends, Path, Query, Request, status

from sqlalchemy.ext.asyncio import AsyncSession

from src.core.database import get_db
from src.core.app_url import resolve_github_user_mirror_return_url
from src.core.app_url import resolve_gitlab_user_mirror_return_url
from src.core.app_url import resolve_bitbucket_user_mirror_return_url
from src.core.app_url import resolve_azure_devops_mirror_return_url
from src.core.dependencies import (
    get_productive_access_policy,
    require_active_billing_for_project_id,
    require_active_billing_for_workspace,
)
from src.domain.auth.models import User
from src.domain.auth.router import get_current_user
from src.domain.projects.repository import (
    ProjectEnvVarRepository,
    ProjectJobOutboxRepository,
    ProjectImportSourceCatalogRepository,
    ProjectRepoRepository,
    ProjectRepository,
    ProjectSettingsRepository,
)
from src.domain.projects.schemas import (
    EnvVarCreate,
    EnvVarResponse,
    EnvVarUpdate,
    FormPolicyResponse,
    FormPolicyUpdate,
    ExternalIndexAdapterSettings,
    ProjectCreate,
    ProjectImportFinalize,
    ProjectRecommendationRequest,
    ProjectRecommendationResponse,
    ProjectImportSourceEndpoint,
    ProjectImportSourceOption,
    ProjectImportStart,
    ProjectImportStartResponse,
    ProjectResponse,
    ProjectUpdate,
    project_to_response,
)
from src.domain.admin.repository import SubscriptionPlanRepository
from src.domain.billing.plan_quota_service import PlanQuotaService
from src.domain.billing.productive_access_policy import ProductiveAccessPolicy
from src.domain.billing.repository import SubscriptionRepository
from src.domain.projects.service import ProjectService
from src.domain.templates.repository import TemplateRepository
from src.domain.workspaces.facades import WorkspaceFacadeImpl
from src.domain.workspaces.repository import MembershipRepository, WorkspaceRepository
from src.domain.github_user_mirror.repository import (
    ProjectUserMirrorRepository,
    WorkspaceUserGithubInstallationRepository,
)
from src.domain.projects.import_source_catalog import IMPORT_SOURCE_DEFAULTS
from src.domain.gitlab_user_mirror.repository import (
    ProjectUserGitlabMirrorRepository,
    WorkspaceUserGitlabConnectionRepository,
)
from src.domain.bitbucket_user_mirror.repository import (
    ProjectUserBitbucketMirrorRepository,
    WorkspaceUserBitbucketConnectionRepository,
)
from src.domain.azure_user_mirror.repository import (
    ProjectUserAzureMirrorRepository,
    WorkspaceUserAzureConnectionRepository,
)

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


from src.domain.projects.discovery_schemas import DiscoveryInput, DiscoveryView, public_discovery
from src.domain.projects.discovery_service import DiscoveryService


@router.get("/projects/{project_id}/discovery", response_model=DiscoveryView)
async def get_project_discovery(
    project_id: UUID, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db),
):
    from src.core.exceptions import ForbiddenError, NotFoundError
    project = await ProjectRepository(db).get_by_id(project_id)
    if not project or project.deleted_at:
        raise NotFoundError("Project not found")
    if not await MembershipRepository(db).has_permission(project.workspace_id, user.id, "projects.write"):
        raise ForbiddenError()
    return public_discovery(project.discovery_json)


@router.post("/projects/{project_id}/discovery", response_model=DiscoveryView)
async def submit_project_discovery(
    body: DiscoveryInput, project_id: UUID = Depends(require_active_billing_for_project_id),
    user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db),
):
    return await DiscoveryService(db).submit(project_id, user.id, body)


async def get_workspace_facade(db: AsyncSession = Depends(get_db)) -> WorkspaceFacadeImpl:
    return WorkspaceFacadeImpl(
        workspace_repo=WorkspaceRepository(db),
        membership_repo=MembershipRepository(db),
    )


async def get_project_service(
    db: AsyncSession = Depends(get_db),
    workspace_facade: WorkspaceFacadeImpl = Depends(get_workspace_facade),
) -> ProjectService:
    ws_repo = WorkspaceRepository(db)
    return ProjectService(
        project_repo=ProjectRepository(db),
        project_repo_repo=ProjectRepoRepository(db),
        settings_repo=ProjectSettingsRepository(db),
        env_var_repo=ProjectEnvVarRepository(db),
        workspace_facade=workspace_facade,
        plan_quota=PlanQuotaService(
            workspace_repo=ws_repo,
            subscription_repo=SubscriptionRepository(db),
            plan_repo=SubscriptionPlanRepository(db),
        ),
        job_outbox_repo=ProjectJobOutboxRepository(db),
        template_repo=TemplateRepository(db),
        github_installation_repo=WorkspaceUserGithubInstallationRepository(db),
        github_mirror_repo=ProjectUserMirrorRepository(db),
        gitlab_connection_repo=WorkspaceUserGitlabConnectionRepository(db),
        gitlab_mirror_repo=ProjectUserGitlabMirrorRepository(db),
        bitbucket_connection_repo=WorkspaceUserBitbucketConnectionRepository(db),
        bitbucket_mirror_repo=ProjectUserBitbucketMirrorRepository(db),
        azure_connection_repo=WorkspaceUserAzureConnectionRepository(db),
        azure_mirror_repo=ProjectUserAzureMirrorRepository(db),
    )


@router.get("/projects/{project_id}/search-index-adapters", response_model=list[ExternalIndexAdapterSettings])
async def list_search_index_adapters(
    project_id: UUID = Depends(require_active_billing_for_project_id),
    user: User = Depends(get_current_user),
    service: ProjectService = Depends(get_project_service),
    db: AsyncSession = Depends(get_db),
):
    project = await service.get_project(project_id, user.id)
    if not project:
        from src.core.exceptions import NotFoundError
        raise NotFoundError("Project not found")
    return [ExternalIndexAdapterSettings(**item) for item in await ProjectSettingsRepository(db).get_search_index_adapters(project_id)]


@router.put("/projects/{project_id}/search-index-adapters", response_model=ExternalIndexAdapterSettings)
async def upsert_search_index_adapter(
    body: ExternalIndexAdapterSettings,
    project_id: UUID = Depends(require_active_billing_for_project_id),
    user: User = Depends(get_current_user),
    service: ProjectService = Depends(get_project_service),
    db: AsyncSession = Depends(get_db),
):
    project = await service.get_project(project_id, user.id)
    if not project:
        from src.core.exceptions import NotFoundError
        raise NotFoundError("Project not found")
    from src.domain.connectors.repository import ConnectorRepository
    try:
        connection_id = UUID(body.connection_id)
    except ValueError as exc:
        from src.core.exceptions import ValidationError
        raise ValidationError("connection_id must reference an active project connector") from exc
    connection = await ConnectorRepository(db).get_connection_for_project(project_id, connection_id)
    if not connection or connection.status != "active":
        from src.core.exceptions import ValidationError
        raise ValidationError("connection_id must reference an active project connector")
    payload = body.model_dump()
    await ProjectSettingsRepository(db).set_search_index_adapter(project_id, payload)
    await db.commit()
    from src.integrations.cloud_control_client import get_cloud_client_for_workspace
    cloud = await get_cloud_client_for_workspace(project.workspace_id, db)
    await cloud.upsert_query_adapter(
        str(project_id), body.entity_key, storage_strategy="p2hs_records",
        connection_id=body.connection_id, index_adapter={"kind": "http_json", **payload},
    )
    return body


@router.delete("/projects/{project_id}/search-index-adapters/{entity_key}")
async def delete_search_index_adapter(
    entity_key: str,
    project_id: UUID = Depends(require_active_billing_for_project_id),
    user: User = Depends(get_current_user),
    service: ProjectService = Depends(get_project_service),
    db: AsyncSession = Depends(get_db),
):
    project = await service.get_project(project_id, user.id)
    if not project:
        from src.core.exceptions import NotFoundError
        raise NotFoundError("Project not found")
    await ProjectSettingsRepository(db).delete_search_index_adapter(project_id, entity_key)
    await db.commit()
    from src.integrations.cloud_control_client import get_cloud_client_for_workspace
    cloud = await get_cloud_client_for_workspace(project.workspace_id, db)
    await cloud.delete_query_adapter(str(project_id), entity_key)
    return {"ok": True}


@router.post("/project/import-start", response_model=ProjectImportStartResponse)
async def start_project_import(
    body: ProjectImportStart,
    request: Request,
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
    workspace_facade: WorkspaceFacadeImpl = Depends(get_workspace_facade),
    policy: ProductiveAccessPolicy = Depends(get_productive_access_policy),
):
    """Start an existing-project import flow and return provider options."""
    from src.core.exceptions import ForbiddenError, ValidationError
    from src.config import get_settings
    from src.domain.azure_user_mirror.service import AzureUserMirrorService
    from src.domain.bitbucket_user_mirror.service import BitbucketUserMirrorService
    from src.domain.github_user_mirror.service import GithubUserMirrorService
    from src.domain.gitlab_user_mirror.service import GitlabUserMirrorService
    from src.domain.projects.repository import ProjectImportSessionRepository

    ws = body.workspace_id
    await policy.require_for_workspace(ws, user.id)
    if not await workspace_facade.check_permission(ws, user.id, "projects.create"):
        raise ForbiddenError()
    if not await workspace_facade.check_permission(ws, user.id, "repos.manage"):
        raise ForbiddenError()

    session = await ProjectImportSessionRepository(db).create(
        workspace_id=ws,
        created_by=user.id,
        provider=body.provider,
    )
    callback_return_url = resolve_github_user_mirror_return_url(
        request,
        redirect_base=body.redirect_base,
    )
    mirror_svc = GithubUserMirrorService(
        WorkspaceUserGithubInstallationRepository(db),
        ProjectUserMirrorRepository(db),
        ProjectRepository(db),
    )
    settings = get_settings()
    github_enabled = True
    install_url = None
    state = None
    try:
        install_url, state = mirror_svc.build_install_url(
            ws,
            user.id,
            callback_return_url,
            import_project_id=session.project_id,
        )
    except ValidationError:
        github_enabled = False
    gitlab_callback_return_url = resolve_gitlab_user_mirror_return_url(
        request,
        redirect_base=body.redirect_base,
    )
    gitlab_redirect_uri = f"{(settings.api_url or str(request.base_url).rstrip('/')).rstrip('/')}/api/integrations/gitlab-user/callback"
    gitlab_svc = GitlabUserMirrorService(
        WorkspaceUserGitlabConnectionRepository(db),
        ProjectUserGitlabMirrorRepository(db),
        ProjectRepository(db),
    )
    gitlab_enabled = bool(
        settings.gitlab_user_oauth_client_id and settings.gitlab_user_oauth_client_secret
    )
    gitlab_connect_url = None
    gitlab_state = None
    if gitlab_enabled:
        gitlab_connect_url, gitlab_state = gitlab_svc.build_connect_url(
            workspace_id=ws,
            user_id=user.id,
            callback_return_url=gitlab_callback_return_url,
            redirect_uri=gitlab_redirect_uri,
            import_project_id=session.project_id,
        )
    bitbucket_callback_return_url = resolve_bitbucket_user_mirror_return_url(
        request,
        redirect_base=body.redirect_base,
    )
    bitbucket_redirect_uri = f"{(settings.api_url or str(request.base_url).rstrip('/')).rstrip('/')}/api/integrations/bitbucket-user/callback"
    bitbucket_svc = BitbucketUserMirrorService(
        WorkspaceUserBitbucketConnectionRepository(db),
        ProjectUserBitbucketMirrorRepository(db),
        ProjectRepository(db),
    )
    bitbucket_enabled = bool(
        settings.bitbucket_user_oauth_client_id and settings.bitbucket_user_oauth_client_secret
    )
    bitbucket_connect_url = None
    bitbucket_state = None
    if bitbucket_enabled:
        bitbucket_connect_url, bitbucket_state = bitbucket_svc.build_connect_url(
            workspace_id=ws,
            user_id=user.id,
            callback_return_url=bitbucket_callback_return_url,
            redirect_uri=bitbucket_redirect_uri,
            import_project_id=session.project_id,
        )
    azure_callback_return_url = resolve_azure_devops_mirror_return_url(
        request,
        redirect_base=body.redirect_base,
    )
    azure_redirect_uri = f"{(settings.api_url or str(request.base_url).rstrip('/')).rstrip('/')}/api/integrations/azure-devops/callback"
    azure_svc = AzureUserMirrorService(
        WorkspaceUserAzureConnectionRepository(db),
        ProjectUserAzureMirrorRepository(db),
        ProjectRepository(db),
    )
    azure_enabled = bool(
        settings.azure_devops_oauth_client_id
        and settings.azure_devops_oauth_client_secret
    )
    azure_connect_url = None
    azure_state = None
    if azure_enabled and body.azure_org_url:
        azure_connect_url, azure_state = azure_svc.build_connect_url(
            workspace_id=ws,
            user_id=user.id,
            azure_org_url=body.azure_org_url,
            callback_return_url=azure_callback_return_url,
            redirect_uri=azure_redirect_uri,
            import_project_id=session.project_id,
        )
    catalog_rows = await ProjectImportSourceCatalogRepository(db).list_visible()
    catalog = [
        {
            "provider": row.provider,
            "label": row.label,
            "icon": row.icon,
        }
        for row in catalog_rows
    ] or IMPORT_SOURCE_DEFAULTS

    runtime_sources = {
        "github": {
            "enabled": github_enabled,
            "install_url": install_url,
            "state": state,
            "endpoints": {
                "authorizations": ProjectImportSourceEndpoint(
                    method="GET",
                    path=f"/api/workspaces/{ws}/github-user/installations",
                ),
                "confirm_authorization": ProjectImportSourceEndpoint(
                    method="POST",
                    path=f"/api/workspaces/{ws}/github-user/installations",
                ),
                "repository_options": ProjectImportSourceEndpoint(
                    method="GET",
                    path=(
                        f"/api/workspaces/{ws}/github-user/installations/"
                        "{authorization_id}/repositories"
                    ),
                ),
                "installations": ProjectImportSourceEndpoint(
                    method="GET",
                    path=f"/api/workspaces/{ws}/github-user/installations",
                ),
                "confirm_installation": ProjectImportSourceEndpoint(
                    method="POST",
                    path=f"/api/workspaces/{ws}/github-user/installations",
                ),
                "repositories": ProjectImportSourceEndpoint(
                    method="GET",
                    path=(
                        f"/api/workspaces/{ws}/github-user/installations/"
                        "{installation_row_id}/repositories"
                    ),
                ),
            }
            if github_enabled
            else {},
        },
        "gitlab": {
            "enabled": gitlab_enabled,
            "install_url": gitlab_connect_url,
            "state": gitlab_state,
            "endpoints": {
                "authorizations": ProjectImportSourceEndpoint(
                    method="GET",
                    path=f"/api/workspaces/{ws}/gitlab-user/connections",
                ),
                "repository_options": ProjectImportSourceEndpoint(
                    method="GET",
                    path=(
                        f"/api/workspaces/{ws}/gitlab-user/connections/"
                        "{authorization_id}/projects"
                    ),
                ),
                "connections": ProjectImportSourceEndpoint(
                    method="GET",
                    path=f"/api/workspaces/{ws}/gitlab-user/connections",
                ),
                "projects": ProjectImportSourceEndpoint(
                    method="GET",
                    path=(
                        f"/api/workspaces/{ws}/gitlab-user/connections/"
                        "{connection_id}/projects"
                    ),
                ),
            }
            if gitlab_enabled
            else {},
        },
        "bitbucket": {
            "enabled": bitbucket_enabled,
            "install_url": bitbucket_connect_url,
            "state": bitbucket_state,
            "endpoints": {
                "authorizations": ProjectImportSourceEndpoint(
                    method="GET",
                    path=f"/api/workspaces/{ws}/bitbucket-user/connections",
                ),
                "repository_options": ProjectImportSourceEndpoint(
                    method="GET",
                    path=(
                        f"/api/workspaces/{ws}/bitbucket-user/connections/"
                        "{authorization_id}/repositories"
                    ),
                ),
                "connections": ProjectImportSourceEndpoint(
                    method="GET",
                    path=f"/api/workspaces/{ws}/bitbucket-user/connections",
                ),
                "repositories": ProjectImportSourceEndpoint(
                    method="GET",
                    path=(
                        f"/api/workspaces/{ws}/bitbucket-user/connections/"
                        "{connection_id}/repositories"
                    ),
                ),
            }
            if bitbucket_enabled
            else {},
        },
        "azure_devops": {
            "enabled": azure_enabled,
            "install_url": azure_connect_url,
            "state": azure_state,
            "endpoints": {
                "authorizations": ProjectImportSourceEndpoint(
                    method="GET",
                    path=f"/api/workspaces/{ws}/azure-devops/connections",
                ),
                "repository_options": ProjectImportSourceEndpoint(
                    method="GET",
                    path=(
                        f"/api/workspaces/{ws}/azure-devops/connections/"
                        "{authorization_id}/repositories"
                    ),
                ),
                "connect_url": ProjectImportSourceEndpoint(
                    method="GET",
                    path=(
                        f"/api/workspaces/{ws}/azure-devops/connect-url"
                        "?azure_org_url={azure_org_url}"
                    ),
                ),
                "connections": ProjectImportSourceEndpoint(
                    method="GET",
                    path=f"/api/workspaces/{ws}/azure-devops/connections",
                ),
                "repositories": ProjectImportSourceEndpoint(
                    method="GET",
                    path=(
                        f"/api/workspaces/{ws}/azure-devops/connections/"
                        "{connection_id}/repositories"
                    ),
                ),
            }
            if azure_enabled
            else {},
        },
    }

    sources = []
    for item in catalog:
        provider = item["provider"]
        runtime = runtime_sources.get(provider, {})
        sources.append(
            ProjectImportSourceOption(
                provider=provider,
                label=item["label"],
                icon=item.get("icon"),
                enabled=bool(runtime.get("enabled", False)),
                install_url=runtime.get("install_url"),
                state=runtime.get("state"),
                endpoints=runtime.get("endpoints") or {},
            )
        )

    await db.commit()
    return ProjectImportStartResponse(
        project_id=session.project_id,
        workspace_id=ws,
        status=session.status,
        sources=sources,
    )


@router.post("/project/import", response_model=ProjectResponse, status_code=status.HTTP_202_ACCEPTED)
async def finalize_project_import(
    body: ProjectImportFinalize,
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
    workspace_facade: WorkspaceFacadeImpl = Depends(get_workspace_facade),
    service: ProjectService = Depends(get_project_service),
    policy: ProductiveAccessPolicy = Depends(get_productive_access_policy),
):
    """Finalize a GitHub repo import and enqueue managed repo setup."""
    from src.core.exceptions import ForbiddenError, NotFoundError, ValidationError
    from src.domain.github_user_mirror.service import GithubUserMirrorService
    from src.domain.projects.repository import ProjectImportSessionRepository

    ws = body.workspace_id
    await policy.require_for_workspace(ws, user.id)
    if not await workspace_facade.check_permission(ws, user.id, "projects.create"):
        raise ForbiddenError()
    if not await workspace_facade.check_permission(ws, user.id, "repos.manage"):
        raise ForbiddenError()

    import_repo = ProjectImportSessionRepository(db)
    session = await import_repo.get(body.project_id)
    if not session or session.workspace_id != ws or session.created_by != user.id:
        raise NotFoundError("Import session not found")
    if session.status == "finalized":
        raise ValidationError("Import session already finalized.")

    installation_row_id = body.workspace_user_github_installation_id
    gitlab_connection_id = body.workspace_user_gitlab_connection_id
    bitbucket_connection_id = body.workspace_user_bitbucket_connection_id
    azure_connection_id = body.workspace_user_azure_connection_id
    github_installation_id = body.installation_id
    if azure_connection_id is not None:
        azure_row = await WorkspaceUserAzureConnectionRepository(db).get_by_id(
            azure_connection_id
        )
        if not azure_row or azure_row.workspace_id != ws:
            raise ValidationError("Invalid Azure DevOps connection for this workspace.")
        source = {
            "kind": "azure_devops_import",
            "workspace_user_azure_connection_id": azure_connection_id,
            "repo_full_name": body.repo_full_name,
            "branch": body.branch,
            "root_dir": body.root_dir,
            "import_env": body.import_env,
        }
    elif bitbucket_connection_id is not None:
        bitbucket_row = await WorkspaceUserBitbucketConnectionRepository(db).get_by_id(
            bitbucket_connection_id
        )
        if not bitbucket_row or bitbucket_row.workspace_id != ws:
            raise ValidationError("Invalid Bitbucket connection for this workspace.")
        source = {
            "kind": "bitbucket_import",
            "workspace_user_bitbucket_connection_id": bitbucket_connection_id,
            "repo_full_name": body.repo_full_name,
            "branch": body.branch,
            "root_dir": body.root_dir,
            "import_env": body.import_env,
        }
    elif gitlab_connection_id is not None:
        gitlab_row = await WorkspaceUserGitlabConnectionRepository(db).get_by_id(gitlab_connection_id)
        if not gitlab_row or gitlab_row.workspace_id != ws:
            raise ValidationError("Invalid GitLab connection for this workspace.")
        source = {
            "kind": "gitlab_import",
            "workspace_user_gitlab_connection_id": gitlab_connection_id,
            "repo_full_name": body.repo_full_name,
            "branch": body.branch,
            "root_dir": body.root_dir,
            "import_env": body.import_env,
        }
    elif installation_row_id is None:
        if not github_installation_id:
            raise ValidationError("installation_id or workspace_user_github_installation_id is required.")
        mirror_svc = GithubUserMirrorService(
            WorkspaceUserGithubInstallationRepository(db),
            ProjectUserMirrorRepository(db),
            ProjectRepository(db),
        )
        row = await mirror_svc.confirm_installation(
            workspace_id=ws,
            user_id=user.id,
            installation_id=github_installation_id,
            state=None,
        )
        installation_row_id = row.id
    else:
        row = await WorkspaceUserGithubInstallationRepository(db).get_by_id(installation_row_id)
        if not row or row.workspace_id != ws:
            raise ValidationError("Invalid GitHub installation for this workspace.")
        github_installation_id = row.github_installation_id
        source = {
            "kind": "github_import",
            "workspace_user_github_installation_id": installation_row_id,
            "repo_full_name": body.repo_full_name,
            "branch": body.branch,
            "root_dir": body.root_dir,
            "import_env": body.import_env,
        }

    project = await service.create_project(
        workspace_id=ws,
        name=body.name,
        project_type=body.type.value,
        description=body.description,
        user_id=user.id,
        project_id=body.project_id,
        source=source,
    )
    await import_repo.update(
        body.project_id,
        status="finalized",
        github_installation_id=github_installation_id,
        workspace_user_github_installation_id=installation_row_id,
        workspace_user_gitlab_connection_id=gitlab_connection_id,
        workspace_user_bitbucket_connection_id=bitbucket_connection_id,
        workspace_user_azure_connection_id=azure_connection_id,
        source_repo_full_name=body.repo_full_name,
        source_branch=body.branch,
        source_root_dir=body.root_dir,
    )
    await db.commit()
    return project_to_response(project)


@router.post(
    "/workspaces/{ws}/projects/recommendation",
    response_model=ProjectRecommendationResponse,
)
async def recommend_project(
    body: ProjectRecommendationRequest,
    ws: UUID = Depends(require_active_billing_for_workspace),
    user: User = Depends(get_current_user),
    service: ProjectService = Depends(get_project_service),
    workspace_facade: WorkspaceFacadeImpl = Depends(get_workspace_facade),
):
    """Resolve intent/template/override inputs into a concrete project type and template."""
    from src.core.exceptions import ForbiddenError

    if not await workspace_facade.check_permission(ws, user.id, "projects.create"):
        raise ForbiddenError()
    out = await service.recommend_project_creation(
        workspace_id=ws,
        requested_type=body.type.value if body.type else None,
        template_id=body.template_id,
        intent=body.intent,
        preferred_stack=body.preferred_stack.value if body.preferred_stack else None,
        stack_selection_mode=body.stack_selection_mode.value,
    )
    return ProjectRecommendationResponse(**out)


@router.post("/workspaces/{ws}/projects", response_model=ProjectResponse, status_code=status.HTTP_202_ACCEPTED)
async def create_project(
    body: ProjectCreate,
    with_template_id: Optional[UUID] = Query(
        None,
        alias="with-template-id",
        description="Optional template catalog id. Defaults to Core's starter template for the project type.",
    ),
    ws: UUID = Depends(require_active_billing_for_workspace),
    user: User = Depends(get_current_user),
    service: ProjectService = Depends(get_project_service),
):
    """Create project (async - returns 202). Requires active workspace billing."""
    project = await service.create_project(
        workspace_id=ws,
        name=body.name,
        project_type=body.type.value if body.type else None,
        description=body.description,
        user_id=user.id,
        template_id=body.template_id or with_template_id,
        discovery_requested=body.discovery_requested,
        source=body.source.model_dump() if body.source else None,
        intent=body.intent,
        preferred_stack=body.preferred_stack.value if body.preferred_stack else None,
        stack_selection_mode=body.stack_selection_mode.value,
    )
    return project_to_response(project)


@router.get("/workspaces/{ws}/projects", response_model=list[ProjectResponse])
async def list_projects(
    ws: UUID = Path(...),
    user: User = Depends(get_current_user),
    service: ProjectService = Depends(get_project_service),
):
    """List projects in workspace."""
    projects = await service.list_projects(ws, user.id)
    return [project_to_response(p) for p in projects]


@router.get("/projects/{project_id}", response_model=ProjectResponse)
async def get_project(
    project_id: UUID = Depends(require_active_billing_for_project_id),
    user: User = Depends(get_current_user),
    service: ProjectService = Depends(get_project_service),
):
    """Get project by ID."""
    project = await service.get_project(project_id, user.id)
    if not project:
        from src.core.exceptions import NotFoundError
        raise NotFoundError("Project not found")
    return project_to_response(project)


@router.patch("/projects/{project_id}", response_model=ProjectResponse)
async def update_project(
    body: ProjectUpdate,
    project_id: UUID = Depends(require_active_billing_for_project_id),
    user: User = Depends(get_current_user),
    service: ProjectService = Depends(get_project_service),
):
    """Update project."""
    project = await service.update_project(
        project_id, user.id, name=body.name, description=body.description
    )
    if not project:
        from src.core.exceptions import NotFoundError
        raise NotFoundError("Project not found")
    return project_to_response(project)


def _normalize_form_policy(body: FormPolicyUpdate) -> dict:
    from src.domain.reference_data.catalog import normalize_country_codes

    try:
        countries = normalize_country_codes(body.countries)
        default_country = (
            normalize_country_codes([body.default_country])[0]
            if body.default_country
            else None
        )
    except ValueError as exc:
        from src.core.exceptions import ValidationError
        raise ValidationError(str(exc)) from exc

    if body.country_mode == "all" and countries:
        from src.core.exceptions import ValidationError
        raise ValidationError("countries must be empty when country_mode is 'all'.")
    if body.country_mode != "all" and not countries:
        from src.core.exceptions import ValidationError
        raise ValidationError("Select at least one country when country_mode is 'include' or 'exclude'.")
    if body.country_mode == "include" and default_country and default_country not in countries:
        from src.core.exceptions import ValidationError
        raise ValidationError("default_country must be one of the included countries.")
    if body.country_mode == "exclude" and default_country and default_country in countries:
        from src.core.exceptions import ValidationError
        raise ValidationError("default_country cannot be an excluded country.")
    return {
        "country_mode": body.country_mode,
        "countries": countries,
        "default_country": default_country,
        "require_subdivision": body.require_subdivision,
        "phone_country_selector": body.phone_country_selector,
    }


@router.get("/projects/{project_id}/form-policy", response_model=FormPolicyResponse)
async def get_form_policy(
    project_id: UUID = Depends(require_active_billing_for_project_id),
    user: User = Depends(get_current_user),
    service: ProjectService = Depends(get_project_service),
    db: AsyncSession = Depends(get_db),
):
    """Return the project policy that governs standardized form country and phone controls."""
    if not await service.get_project(project_id, user.id):
        from src.core.exceptions import NotFoundError
        raise NotFoundError("Project not found")
    policy = await ProjectSettingsRepository(db).get_form_policy(project_id)
    return FormPolicyResponse(**policy)


@router.put("/projects/{project_id}/form-policy", response_model=FormPolicyResponse)
async def update_form_policy(
    body: FormPolicyUpdate,
    project_id: UUID = Depends(require_active_billing_for_project_id),
    user: User = Depends(get_current_user),
    service: ProjectService = Depends(get_project_service),
    db: AsyncSession = Depends(get_db),
):
    """Set countries and international phone behavior without storing values as environment variables."""
    if not await service.get_project(project_id, user.id):
        from src.core.exceptions import NotFoundError
        raise NotFoundError("Project not found")
    policy = _normalize_form_policy(body)
    await ProjectSettingsRepository(db).set_form_policy(project_id, policy)
    await db.commit()
    return FormPolicyResponse(**policy)


@router.delete("/projects/{project_id}")
async def delete_project(
    project_id: UUID = Depends(require_active_billing_for_project_id),
    user: User = Depends(get_current_user),
    service: ProjectService = Depends(get_project_service),
):
    """Delete project."""
    await service.delete_project(project_id, user.id)
    return {"message": "Project deleted"}


def _env_var_to_response(ev) -> EnvVarResponse:
    return EnvVarResponse(
        id=ev.id,
        key=ev.key,
        environment=ev.environment,
        visibility=ev.visibility,
        created_at=ev.created_at,
    )


@router.get("/projects/{project_id}/env-vars", response_model=list[EnvVarResponse])
async def list_env_vars(
    project_id: UUID = Depends(require_active_billing_for_project_id),
    user: User = Depends(get_current_user),
    service: ProjectService = Depends(get_project_service),
    environment: Optional[str] = Query(
        None,
        description="Filter: include vars for this host plus `all` (e.g. `production`, `preview`).",
    ),
):
    """List project env vars (key names and metadata only — values are never returned)."""
    rows = await service.list_env_vars(
        project_id=project_id, user_id=user.id, environment=environment
    )
    return [_env_var_to_response(ev) for ev in rows]


@router.post("/projects/{project_id}/env-vars", response_model=EnvVarResponse, status_code=status.HTTP_201_CREATED)
async def add_env_var(
    body: EnvVarCreate,
    project_id: UUID = Depends(require_active_billing_for_project_id),
    user: User = Depends(get_current_user),
    service: ProjectService = Depends(get_project_service),
):
    """Create an env var (encrypted at rest). Enqueues CF Pages env sync."""
    ev = await service.add_env_var(
        project_id=project_id,
        user_id=user.id,
        key=body.key,
        value=body.value,
        environment=body.environment,
        visibility=body.visibility,
    )
    from src.workers.enqueue import enqueue_env_sync
    await enqueue_env_sync(str(project_id))
    return _env_var_to_response(ev)


@router.patch("/projects/{project_id}/env-vars/{env_var_id}", response_model=EnvVarResponse)
async def update_env_var(
    env_var_id: UUID,
    body: EnvVarUpdate,
    project_id: UUID = Depends(require_active_billing_for_project_id),
    user: User = Depends(get_current_user),
    service: ProjectService = Depends(get_project_service),
):
    """Rotate the value (and optionally change visibility) for an existing env var. Key and environment are immutable."""
    ev = await service.update_env_var(
        project_id=project_id,
        env_var_id=env_var_id,
        user_id=user.id,
        value=body.value,
        visibility=body.visibility,
    )
    if not ev:
        from src.core.exceptions import NotFoundError
        raise NotFoundError("Environment variable not found")
    from src.workers.enqueue import enqueue_env_sync
    await enqueue_env_sync(str(project_id))
    return _env_var_to_response(ev)


@router.delete("/projects/{project_id}/env-vars/{env_var_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_env_var(
    env_var_id: UUID,
    project_id: UUID = Depends(require_active_billing_for_project_id),
    user: User = Depends(get_current_user),
    service: ProjectService = Depends(get_project_service),
):
    """Remove an env var. Enqueues CF Pages env sync to clear the binding."""
    await service.delete_env_var(
        project_id=project_id, env_var_id=env_var_id, user_id=user.id
    )
    from src.workers.enqueue import enqueue_env_sync
    await enqueue_env_sync(str(project_id))
    return None
