"""Persistence and resolution for the DB-driven Codegen provider registry."""
from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Optional
from uuid import UUID, uuid4

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from src.core.security.encryption import decrypt_value, encrypt_value
from src.domain.codegen.provider_models import (
    CodegenCredential, CodegenExecutionSnapshot, CodegenModel, CodegenProjectConfig,
    CodegenProvider, CodegenUsage, CodegenWorkspaceConfig,
)
from src.domain.projects.models import Project


TRUSTED_PROVIDER_DEFINITIONS = {
    "cursor": {"adapter_key": "cursor_cli", "executable": "agent", "install_strategy_key": "cursor_install"},
    "openai": {"adapter_key": "openai_api", "executable": "openai", "install_strategy_key": "openai_api"},
    "codex": {"adapter_key": "codex_cli", "executable": "codex", "install_strategy_key": "codex_install"},
    "claude": {"adapter_key": "claude_cli", "executable": "claude", "install_strategy_key": "claude_install"},
    "gemini": {"adapter_key": "gemini_cli", "executable": "gemini", "install_strategy_key": "gemini_install"},
    "qwen": {"adapter_key": "qwen_cli", "executable": "qwen", "install_strategy_key": "qwen_install"},
}
IMPLEMENTED_PROVIDER_SLUGS = frozenset({"cursor", "openai", "codex", "claude"})


@dataclass(frozen=True)
class ResolvedCodegenConfig:
    provider: CodegenProvider
    model: CodegenModel
    funding_source: str
    credential: Optional[CodegenCredential]

    def secret(self) -> Optional[str]:
        return decrypt_value(self.credential.encrypted_secret) if self.credential else None


class CodegenProviderRepository:
    def __init__(self, db: AsyncSession): self.db = db

    async def list_providers(self, *, enabled_only: bool = False) -> list[CodegenProvider]:
        stmt = select(CodegenProvider).order_by(CodegenProvider.display_name)
        if enabled_only: stmt = stmt.where(CodegenProvider.enabled.is_(True))
        return list((await self.db.execute(stmt)).scalars().all())

    async def get_provider(self, provider_id: UUID) -> Optional[CodegenProvider]:
        return await self.db.get(CodegenProvider, provider_id)

    async def get_provider_by_slug(self, slug: str) -> Optional[CodegenProvider]:
        return (await self.db.execute(select(CodegenProvider).where(CodegenProvider.slug == slug))).scalar_one_or_none()

    async def create_provider(self, **fields) -> CodegenProvider:
        row = CodegenProvider(id=uuid4(), **fields); self.db.add(row); await self.db.flush(); return row

    async def update_provider(self, row: CodegenProvider, **fields) -> CodegenProvider:
        for key, value in fields.items(): setattr(row, key, value)
        await self.db.flush(); return row

    async def list_models(self, provider_id: Optional[UUID] = None, *, enabled_only: bool = False) -> list[CodegenModel]:
        stmt = select(CodegenModel).order_by(CodegenModel.display_name)
        if provider_id: stmt = stmt.where(CodegenModel.provider_id == provider_id)
        if enabled_only: stmt = stmt.where(CodegenModel.enabled.is_(True))
        return list((await self.db.execute(stmt)).scalars().all())

    async def get_model(self, model_id: UUID) -> Optional[CodegenModel]: return await self.db.get(CodegenModel, model_id)

    async def create_model(self, **fields) -> CodegenModel:
        row = CodegenModel(id=uuid4(), **fields); self.db.add(row); await self.db.flush(); return row

    async def update_model(self, row: CodegenModel, **fields) -> CodegenModel:
        for key, value in fields.items(): setattr(row, key, value)
        await self.db.flush(); return row

    async def list_credentials(self, workspace_id: UUID) -> list[CodegenCredential]:
        stmt = select(CodegenCredential).where(CodegenCredential.workspace_id == workspace_id).order_by(CodegenCredential.created_at.desc())
        return list((await self.db.execute(stmt)).scalars().all())

    async def create_credential(self, *, workspace_id: UUID, provider_id: UUID, label: str, secret: str, created_by: Optional[UUID]) -> CodegenCredential:
        clean = secret.strip()
        row = CodegenCredential(id=uuid4(), workspace_id=workspace_id, provider_id=provider_id, label=label.strip(), encrypted_secret=encrypt_value(clean), last_four=clean[-4:] if len(clean) >= 4 else clean, created_by=created_by)
        self.db.add(row); await self.db.flush(); return row


class CodegenConfigResolver:
    """Resolve workspace defaults and project overrides without returning secrets to callers."""
    def __init__(self, db: AsyncSession): self.db = db; self.repo = CodegenProviderRepository(db)

    async def resolve_optional(self, project_id: UUID) -> Optional[ResolvedCodegenConfig]:
        project = await self.db.get(Project, project_id)
        if not project:
            raise ValueError("project_not_found")
        workspace_cfg = (await self.db.execute(select(CodegenWorkspaceConfig).where(CodegenWorkspaceConfig.workspace_id == project.workspace_id))).scalar_one_or_none()
        project_cfg = (await self.db.execute(select(CodegenProjectConfig).where(CodegenProjectConfig.project_id == project_id))).scalar_one_or_none()
        if not workspace_cfg and not project_cfg:
            return None
        return await self.resolve_for_project(project_id)

    async def resolve_for_project(self, project_id: UUID) -> ResolvedCodegenConfig:
        project = await self.db.get(Project, project_id)
        if not project: raise ValueError("project_not_found")
        workspace_cfg = (await self.db.execute(select(CodegenWorkspaceConfig).where(CodegenWorkspaceConfig.workspace_id == project.workspace_id))).scalar_one_or_none()
        project_cfg = (await self.db.execute(select(CodegenProjectConfig).where(CodegenProjectConfig.project_id == project_id))).scalar_one_or_none()
        provider_id = (project_cfg.provider_id if project_cfg and project_cfg.provider_id else workspace_cfg.provider_id if workspace_cfg else None)
        model_id = (project_cfg.model_id if project_cfg and project_cfg.model_id else workspace_cfg.model_id if workspace_cfg else None)
        funding = (project_cfg.funding_source if project_cfg and project_cfg.funding_source else workspace_cfg.funding_source if workspace_cfg else "p2hs")
        credential_id = project_cfg.credential_id if project_cfg and project_cfg.credential_id else workspace_cfg.credential_id if workspace_cfg else None
        if not provider_id or not model_id: raise ValueError("codegen_configuration_incomplete")
        provider = await self.repo.get_provider(provider_id); model = await self.repo.get_model(model_id)
        if not provider or not model or not provider.enabled or not model.enabled or model.provider_id != provider.id: raise ValueError("codegen_provider_or_model_unavailable")
        credential = await self.db.get(CodegenCredential, credential_id) if credential_id else None
        if funding == "byok":
            if not provider.available_byok or not credential or credential.workspace_id != project.workspace_id or credential.provider_id != provider.id or credential.status != "active": raise ValueError("active_byok_credential_required")
        elif funding != "p2hs": raise ValueError("invalid_codegen_funding_source")
        return ResolvedCodegenConfig(provider=provider, model=model, funding_source=funding, credential=credential)

    async def create_snapshot(self, run_id: UUID, resolved: ResolvedCodegenConfig, *, adapter_version: Optional[str] = None, cli_version: Optional[str] = None, execution=None, pricing_version: str = "github-linux-2026-01") -> CodegenExecutionSnapshot:
        fields = execution.snapshot() if execution else {"provider": "github", "target": "hosted", "runner_class": "linux_standard", "execution_config_version": "1"}
        row = CodegenExecutionSnapshot(run_id=run_id, provider_slug=resolved.provider.slug, model_key=resolved.model.model_key, funding_source=resolved.funding_source, credential_id=resolved.credential.id if resolved.credential else None, adapter_version=adapter_version, cli_version=cli_version, execution_provider=fields["provider"], execution_target=fields["target"], runner_class=fields["runner_class"], execution_config_version=fields["execution_config_version"], pricing_version=pricing_version)
        self.db.add(row); await self.db.flush(); return row

    async def record_usage(self, run_id: UUID, funding_source: str, **fields) -> CodegenUsage:
        row = CodegenUsage(id=uuid4(), run_id=run_id, funding_source=funding_source, wallet_debit=0 if funding_source == "byok" else int(fields.pop("wallet_debit", 0)), **fields)
        self.db.add(row); await self.db.flush(); return row
