"""Codegen provider catalog, workspace credential, and project override APIs."""
from __future__ import annotations

from uuid import UUID, uuid4

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

from src.core.database import get_db
from src.core.dependencies import (
    require_active_billing_for_project,
    require_active_billing_for_workspace_id,
    require_project_codegen_admin,
    require_workspace_codegen_admin,
)
from src.domain.admin.router import require_admin
from src.domain.auth.models import User
from src.domain.auth.router import get_current_user
from src.domain.codegen.provider_models import CodegenProjectConfig, CodegenWorkspaceConfig
from src.domain.codegen.provider_registry import IMPLEMENTED_PROVIDER_SLUGS, TRUSTED_PROVIDER_DEFINITIONS, CodegenProviderRepository
from src.domain.codegen.provider_schemas import (
    CodegenConfigResponse, CodegenConfigUpdate, CodegenCredentialCreate, CodegenCredentialResponse,
    CodegenModelCreate, CodegenModelResponse, CodegenModelUpdate, CodegenProviderCreate,
    CodegenProviderResponse, CodegenProviderUpdate,
)

admin_router = APIRouter(prefix="/api/admin/codegen", tags=["admin", "codegen-catalog"])
workspace_router = APIRouter(prefix="/api/workspaces/{workspace_id}/codegen", tags=["codegen"])
project_router = APIRouter(prefix="/api/projects/{project}/codegen", tags=["codegen"])


@admin_router.get("/providers", response_model=list[CodegenProviderResponse])
async def list_providers(_: User = Depends(require_admin), db: AsyncSession = Depends(get_db)):
    return await CodegenProviderRepository(db).list_providers()


@admin_router.post("/providers", response_model=CodegenProviderResponse, status_code=201)
async def create_provider(body: CodegenProviderCreate, _: User = Depends(require_admin), db: AsyncSession = Depends(get_db)):
    trusted = TRUSTED_PROVIDER_DEFINITIONS.get(body.slug)
    if not trusted or any(getattr(body, key) != value for key, value in trusted.items()):
        raise HTTPException(422, "Provider executable, adapter, and install strategy must be defined in trusted Core code")
    if body.enabled and body.slug not in IMPLEMENTED_PROVIDER_SLUGS:
        raise HTTPException(422, "Provider adapter is not implemented in trusted Core code yet")
    return await CodegenProviderRepository(db).create_provider(**body.model_dump())


@admin_router.patch("/providers/{provider_id}", response_model=CodegenProviderResponse)
async def update_provider(provider_id: UUID, body: CodegenProviderUpdate, _: User = Depends(require_admin), db: AsyncSession = Depends(get_db)):
    repo = CodegenProviderRepository(db); row = await repo.get_provider(provider_id)
    if not row: raise HTTPException(404, "Provider not found")
    changes = body.model_dump(exclude_unset=True)
    trusted = TRUSTED_PROVIDER_DEFINITIONS.get(row.slug)
    if changes.get("enabled") and row.slug not in IMPLEMENTED_PROVIDER_SLUGS:
        raise HTTPException(422, "Provider adapter is not implemented in trusted Core code yet")
    for key in ("adapter_key", "executable", "install_strategy_key"):
        if key in changes and (not trusted or changes[key] != trusted[key]):
            raise HTTPException(422, "Executable, adapter, and install strategy are trusted-code fields")
    return await repo.update_provider(row, **changes)


@admin_router.get("/providers/{provider_id}/models", response_model=list[CodegenModelResponse])
async def list_models(provider_id: UUID, _: User = Depends(require_admin), db: AsyncSession = Depends(get_db)):
    return await CodegenProviderRepository(db).list_models(provider_id)


@admin_router.post("/providers/{provider_id}/models", response_model=CodegenModelResponse, status_code=201)
async def create_model(provider_id: UUID, body: CodegenModelCreate, _: User = Depends(require_admin), db: AsyncSession = Depends(get_db)):
    repo = CodegenProviderRepository(db)
    if not await repo.get_provider(provider_id): raise HTTPException(404, "Provider not found")
    return await repo.create_model(provider_id=provider_id, **body.model_dump())


@admin_router.patch("/models/{model_id}", response_model=CodegenModelResponse)
async def update_model(model_id: UUID, body: CodegenModelUpdate, _: User = Depends(require_admin), db: AsyncSession = Depends(get_db)):
    repo = CodegenProviderRepository(db); row = await repo.get_model(model_id)
    if not row: raise HTTPException(404, "Model not found")
    return await repo.update_model(row, **body.model_dump(exclude_unset=True))


@workspace_router.get("/providers", response_model=list[CodegenProviderResponse])
async def list_workspace_providers(workspace_id: UUID = Depends(require_active_billing_for_workspace_id), _: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
    return await CodegenProviderRepository(db).list_providers(enabled_only=True)


@workspace_router.get("/credentials", response_model=list[CodegenCredentialResponse])
async def list_credentials(workspace_id: UUID = Depends(require_active_billing_for_workspace_id), _: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
    return await CodegenProviderRepository(db).list_credentials(workspace_id)


@workspace_router.post("/credentials", response_model=CodegenCredentialResponse, status_code=201)
async def create_credential(body: CodegenCredentialCreate, workspace_id: UUID = Depends(require_workspace_codegen_admin), _: UUID = Depends(require_active_billing_for_workspace_id), user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
    repo = CodegenProviderRepository(db); provider = await repo.get_provider(body.provider_id)
    if not provider or not provider.enabled or not provider.available_byok: raise HTTPException(422, "Provider does not accept BYOK credentials")
    return await repo.create_credential(workspace_id=workspace_id, provider_id=body.provider_id, label=body.label, secret=body.api_key, created_by=user.id)


@project_router.get("/config", response_model=CodegenConfigResponse)
async def get_project_config(project: UUID = Depends(require_active_billing_for_project), _: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
    row = (await db.execute(select(CodegenProjectConfig).where(CodegenProjectConfig.project_id == project))).scalar_one_or_none()
    return CodegenConfigResponse.model_validate(row, from_attributes=True) if row else CodegenConfigResponse()


@project_router.put("/config", response_model=CodegenConfigResponse)
async def put_project_config(body: CodegenConfigUpdate, project: UUID = Depends(require_project_codegen_admin), _: UUID = Depends(require_active_billing_for_project), __: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
    if body.funding_source not in {"p2hs", "byok"}: raise HTTPException(422, "funding_source must be p2hs or byok")
    row = (await db.execute(select(CodegenProjectConfig).where(CodegenProjectConfig.project_id == project))).scalar_one_or_none()
    if not row:
        row = CodegenProjectConfig(id=uuid4(), project_id=project)
        db.add(row)
    for key, value in body.model_dump().items(): setattr(row, key, value)
    await db.flush()
    return CodegenConfigResponse.model_validate(row, from_attributes=True)


@workspace_router.get("/config", response_model=CodegenConfigResponse)
async def get_workspace_config(workspace_id: UUID = Depends(require_active_billing_for_workspace_id), _: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
    row = (await db.execute(select(CodegenWorkspaceConfig).where(CodegenWorkspaceConfig.workspace_id == workspace_id))).scalar_one_or_none()
    return CodegenConfigResponse.model_validate(row, from_attributes=True) if row else CodegenConfigResponse()


@workspace_router.put("/config", response_model=CodegenConfigResponse)
async def put_workspace_config(body: CodegenConfigUpdate, workspace_id: UUID = Depends(require_workspace_codegen_admin), _: UUID = Depends(require_active_billing_for_workspace_id), __: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
    if body.funding_source not in {"p2hs", "byok"}: raise HTTPException(422, "funding_source must be p2hs or byok")
    row = (await db.execute(select(CodegenWorkspaceConfig).where(CodegenWorkspaceConfig.workspace_id == workspace_id))).scalar_one_or_none()
    if not row:
        row = CodegenWorkspaceConfig(id=uuid4(), workspace_id=workspace_id)
        db.add(row)
    for key, value in body.model_dump().items(): setattr(row, key, value)
    await db.flush()
    return CodegenConfigResponse.model_validate(row, from_attributes=True)
