"""edge_functions domain — Pydantic schemas."""
from __future__ import annotations

from datetime import datetime
from typing import Any, Dict, List, Literal, Optional
from uuid import UUID

from pydantic import BaseModel, Field

TriggerType = Literal["http", "event", "schedule", "webhook"]
Runtime = Literal["js", "python"]
FunctionStatus = Literal["pending", "deploying", "active", "error", "disabled"]
AccessPolicy = Literal["public", "authenticated", "private"]
VersionStatus = Literal["pending", "deploying", "deployed", "rolled_back", "failed"]
RuntimeVersionStatus = Literal["draft", "canary", "stable", "deprecated"]


# ---------------------------------------------------------------------------
# Function Library
# ---------------------------------------------------------------------------


LibraryStatus = Literal["draft", "published", "deprecated", "disabled"]
LibraryVisibility = Literal["public", "private", "internal", "enterprise"]
SemverChannel = Literal["alpha", "beta", "stable"]
ExecutionMode = Literal["sync", "async"]
MinimumPlan = Literal["free", "starter", "pro", "enterprise"]


class FunctionLibraryResponse(BaseModel):
    id: UUID
    name: str
    slug: str
    description: Optional[str] = None
    category: Optional[str] = None
    runtime: str
    trigger_type: str
    supported_trigger_types: Optional[List[str]] = None
    status: str
    visibility: str
    version: str
    semver_channel: str
    minimum_plan: str
    execution_mode: str
    timeout_ms: int
    max_retries: int
    idempotency_required: bool
    required_integrations: Optional[List[str]] = None
    required_secrets: Optional[List[str]] = None
    required_capabilities: Optional[List[str]] = None
    required_bindings: Optional[List[str]] = None
    tags: Optional[List[str]] = None
    input_schema: Optional[Dict[str, Any]] = None
    output_schema: Optional[Dict[str, Any]] = None
    permission_scope: Optional[List[str]] = None
    external_access: Optional[Dict[str, Any]] = None
    trigger_config_schema: Optional[Dict[str, Any]] = None
    codegen_hints: Optional[Dict[str, Any]] = None
    observability_config: Optional[Dict[str, Any]] = None
    billing_config: Optional[Dict[str, Any]] = None
    code_template: Optional[str] = None
    deprecated_at: Optional[datetime] = None
    replacement_slug: Optional[str] = None
    install_count: int = 0
    is_official: bool

    model_config = {"from_attributes": True}


class FunctionLibraryVersionResponse(BaseModel):
    id: UUID
    library_function_id: UUID
    version: str
    semver_channel: str
    code_template: Optional[str] = None
    changelog: Optional[str] = None
    published_at: Optional[datetime] = None
    created_at: datetime

    model_config = {"from_attributes": True}


class CreateLibraryFunctionRequest(BaseModel):
    name: str = Field(..., max_length=120)
    slug: str = Field(..., max_length=64, pattern=r"^[a-z][a-z0-9-]*$")
    description: Optional[str] = None
    category: Optional[str] = None
    runtime: Runtime = "js"
    trigger_type: TriggerType = "http"
    supported_trigger_types: Optional[List[str]] = None
    status: LibraryStatus = "draft"
    visibility: LibraryVisibility = "public"
    version: str = Field("1.0.0", max_length=20)
    semver_channel: SemverChannel = "stable"
    minimum_plan: MinimumPlan = "free"
    execution_mode: ExecutionMode = "sync"
    timeout_ms: int = 10000
    max_retries: int = 3
    idempotency_required: bool = False
    required_integrations: Optional[List[str]] = None
    required_secrets: Optional[List[str]] = None
    required_capabilities: Optional[List[str]] = None
    required_bindings: Optional[List[str]] = None
    tags: Optional[List[str]] = None
    input_schema: Optional[Dict[str, Any]] = None
    output_schema: Optional[Dict[str, Any]] = None
    permission_scope: Optional[List[str]] = None
    external_access: Optional[Dict[str, Any]] = None
    trigger_config_schema: Optional[Dict[str, Any]] = None
    codegen_hints: Optional[Dict[str, Any]] = None
    observability_config: Optional[Dict[str, Any]] = None
    billing_config: Optional[Dict[str, Any]] = None
    code_template: Optional[str] = None
    code_bundle_ref: Optional[str] = Field(None, description="R2 object key for compiled bundle")


class UpdateLibraryFunctionRequest(BaseModel):
    name: Optional[str] = Field(None, max_length=120)
    description: Optional[str] = None
    category: Optional[str] = None
    status: Optional[LibraryStatus] = None
    visibility: Optional[LibraryVisibility] = None
    minimum_plan: Optional[MinimumPlan] = None
    tags: Optional[List[str]] = None
    codegen_hints: Optional[Dict[str, Any]] = None
    observability_config: Optional[Dict[str, Any]] = None
    billing_config: Optional[Dict[str, Any]] = None


class PublishLibraryFunctionRequest(BaseModel):
    changelog: Optional[str] = None


class DeprecateLibraryFunctionRequest(BaseModel):
    replacement_slug: Optional[str] = None


class CreateLibraryVersionRequest(BaseModel):
    version: str = Field(..., max_length=20, pattern=r"^\d+\.\d+(\.\d+)?$")
    semver_channel: SemverChannel = "stable"
    code_template: Optional[str] = None
    code_bundle_ref: Optional[str] = None
    changelog: Optional[str] = None


class FunctionLibraryUpsertResult(BaseModel):
    """Result of manifest-driven upsert (seed, admin import, or API)."""

    slug: str
    created: bool
    updated: bool
    skipped: bool = False
    skip_reason: Optional[str] = None
    dry_run: bool = False
    manifest_hash: Optional[str] = None
    metadata_changed: list[str] = []
    versioned_changed: list[str] = []
    requires_new_version: bool = False
    published: bool = False
    current_version_id: Optional[UUID] = None


class ManifestDiffResponse(BaseModel):
    """Compare DB row to a seed file or an explicit manifest."""

    slug: str
    status: Literal["unchanged", "changed", "not_found", "missing_seed", "error"]
    metadata_changes: list[str] = []
    versioned_changes: list[str] = []
    requires_new_version: bool = False
    admin_override: bool = False
    message: Optional[str] = None


class LibraryManifestImportBody(BaseModel):
    """Raw manifest JSON (same shape as seed YAML)."""

    manifest: Dict[str, Any]


class ManifestValidateResponse(BaseModel):
    ok: bool
    errors: List[str] = []


class RestoreLibraryBody(BaseModel):
    to_status: Literal["draft", "published"] = "draft"


# ---------------------------------------------------------------------------
# Project Functions
# ---------------------------------------------------------------------------


class CreateFunctionRequest(BaseModel):
    slug: str = Field(
        ..., max_length=64, pattern=r"^[a-z][a-z0-9-]*$",
        description="Unique slug within the project"
    )
    name: str = Field(..., max_length=120)
    description: Optional[str] = None
    runtime: Runtime = "js"
    trigger_type: TriggerType = "http"
    trigger_config: Optional[Dict[str, Any]] = None
    access_policy: AccessPolicy = "authenticated"
    code_bundle_ref: str = Field(..., description="R2 key for the initial code bundle")
    secret_refs: Optional[List[str]] = None
    created_by: Literal["codegen", "user"] = "user"


class DeployFromLibraryRequest(BaseModel):
    library_fn_id: UUID
    slug: str = Field(
        ..., max_length=64, pattern=r"^[a-z][a-z0-9-]*$",
        description="Slug to use for this project's copy of the function"
    )
    trigger_config: Optional[Dict[str, Any]] = None
    access_policy: AccessPolicy = "authenticated"
    secret_refs: Optional[List[str]] = None
    created_by: Literal["codegen", "user"] = "user"
    pack_slug: Optional[str] = None
    pack_version: Optional[str] = None


class UpdateFunctionCodeRequest(BaseModel):
    code_bundle_ref: str = Field(..., description="R2 key for the new code bundle")


class UpdateFunctionSecretsRequest(BaseModel):
    secret_refs: List[str]


class FunctionVersionResponse(BaseModel):
    id: UUID
    function_id: UUID
    version_number: int
    code_bundle_ref: Optional[str] = None
    library_fn_id: Optional[UUID] = None
    library_fn_version: Optional[str] = None
    status: str
    deployed_at: Optional[datetime] = None
    rollback_from_version: Optional[int] = None
    created_at: datetime

    model_config = {"from_attributes": True}


class FunctionResponse(BaseModel):
    id: UUID
    project_id: UUID
    slug: str
    name: str
    description: Optional[str] = None
    runtime: str
    trigger_type: str
    trigger_config: Optional[Dict[str, Any]] = None
    access_policy: str
    active_version_id: Optional[UUID] = None
    status: str
    library_fn_id: Optional[UUID] = None
    editable: bool = True
    secret_refs: Optional[List[str]] = None
    created_by: str
    pack_slug: Optional[str] = None
    pack_version: Optional[str] = None
    created_at: datetime
    updated_at: datetime

    model_config = {"from_attributes": True}


# ---------------------------------------------------------------------------
# Webhook Endpoints
# ---------------------------------------------------------------------------


class CreateWebhookEndpointRequest(BaseModel):
    provider: str = Field(..., max_length=40)
    events: Optional[List[str]] = None
    function_slug: Optional[str] = Field(None, max_length=64)
    signing_secret: Optional[str] = Field(
        None, description="Provider signing secret — stored encrypted"
    )


class WebhookEndpointResponse(BaseModel):
    id: UUID
    project_id: UUID
    wh_token: str
    webhook_url: str = Field(..., description="Full URL: cloud.p2hs.com/wh/{token}")
    provider: str
    events: Optional[List[str]] = None
    auto_registered: bool
    registered_at: Optional[datetime] = None
    function_slug: Optional[str] = None
    is_active: bool
    created_at: datetime

    model_config = {"from_attributes": True}


# ---------------------------------------------------------------------------
# Worker Runtime Versions (admin)
# ---------------------------------------------------------------------------


class CreateRuntimeVersionRequest(BaseModel):
    version: str = Field(..., max_length=20, pattern=r"^\d+\.\d+\.\d+$")
    bundle_ref: str = Field(..., description="R2 key for the runtime bundle")
    release_notes: Optional[str] = None


class UpdateRuntimeVersionRequest(BaseModel):
    status: RuntimeVersionStatus


class RuntimeVersionResponse(BaseModel):
    id: UUID
    version: str
    bundle_ref: str
    status: str
    release_notes: Optional[str] = None
    released_at: Optional[datetime] = None
    deprecated_at: Optional[datetime] = None
    created_at: datetime

    model_config = {"from_attributes": True}
