"""Ground the private build plan in the same manifest codegen consumes."""

import hashlib
import json
import re

from pydantic import BaseModel, ConfigDict, Field


class WorkflowBinding(BaseModel):
    model_config = ConfigDict(extra="forbid")
    requirement: str = Field(max_length=2000)
    port_ids: list[str] = Field(max_length=30)
    implementation: str = Field(max_length=3000)
    acceptance_checks: list[str] = Field(max_length=12)
    gaps: list[str] = Field(max_length=12)


class DiscoveryBuildPlan(BaseModel):
    model_config = ConfigDict(extra="forbid")
    workflows: list[WorkflowBinding] = Field(min_length=1, max_length=30)
    provisioning: list[str] = Field(max_length=30)
    setup_requirements: list[str] = Field(max_length=20)


def discovery_contract(backend: dict) -> dict:
    """Explicit projection: exclude identities, credentials and free-form integration config."""
    keys = (
        "contract_name",
        "contract_version",
        "api_surface",
        "bff_conventions",
        "platform_sdk",
        "runtime_env",
        "database",
        "storage",
        "provisioning",
        "query_dsl",
        "data_import",
        "rules",
        "functions",
        "realtime",
        "ai",
    )
    contract = {k: backend[k] for k in keys if k in backend}
    auth_keys = (
        "enabled",
        "providers",
        "allow_signup",
        "custom_roles",
        "user_directory",
        "super_admin_role",
        "admin_role",
        "customer_role",
        "admin_panel_path",
        "customer_login_path",
    )
    contract["auth"] = {k: v for k, v in backend.get("auth", {}).items() if k in auth_keys}
    routes = ((backend.get("runtime_contract") or {}).get("routes") or {})
    if routes.get("admin_panel_path") and "admin_panel_path" not in contract["auth"]:
        contract["auth"]["admin_panel_path"] = routes["admin_panel_path"]
    if routes.get("customer_login_path") and "customer_login_path" not in contract["auth"]:
        contract["auth"]["customer_login_path"] = routes["customer_login_path"]
    packs = backend.get("feature_packs", {})
    contract["feature_packs"] = {
        "catalog": packs.get("catalog", []),
        "installed": [
            {k: v for k, v in p.items() if k in {"slug", "version", "status"}}
            for p in packs.get("installed", [])
        ],
    }
    contract["limitations"] = [
        "Discovery snapshot, not an authorization grant. The execution manifest takes precedence.",
        "Provider configuration and identities are omitted; never infer integration readiness.",
        "Only listed ports are supported. Missing request/response schemas are unknown; "
        "do not invent them.",
        "Starter changes must preserve platform ports; verify listed artifacts at execution.",
    ]
    # Copy so callers cannot mutate the source manifest through nested objects.
    return json.loads(json.dumps(contract))


async def load_discovery_contract(db, project_id):
    from src.domain.codegen.platform_backend import build_platform_backend_capabilities
    from src.domain.manifest.repository import ManifestRunRepository
    from src.domain.manifest.service import ManifestService

    manifest = await ManifestService(db, ManifestRunRepository(db)).get_manifest(
        project_id, compact=False
    )
    return discovery_contract(build_platform_backend_capabilities(manifest))


def attach_build_plan(result, plan: DiscoveryBuildPlan, contract: dict) -> dict:
    # Endpoint literals belong to Core's rendering, not model-authored prose.
    if re.search(r"/(?:api|v1)(?:/|\b)", plan.model_dump_json()):
        raise ValueError("model_authored_endpoint")
    ports = {
        operation["port_id"]: operation
        for group in contract.get("api_surface", {}).values()
        for operation in group.values()
    }
    selected = {port for workflow in plan.workflows for port in workflow.port_ids}
    if selected - ports.keys():
        raise ValueError("unadvertised_discovery_port")
    fingerprint = hashlib.sha256(json.dumps(contract, sort_keys=True).encode()).hexdigest()
    sections = [
        result.specification,
        "## Cloud implementation plan\n"
        "Use p2hs/discovery-cloud-contract.json for the complete discovery contract. "
        "The current run's PlatformBackendCapabilities overrides this snapshot. "
        "Paths and auth policies below are resolved by Core, not generated by the model. "
        "Missing capabilities must be reported, never invented or replaced with local backends.",
        f"Contract SHA-256: {fingerprint}",
    ]
    for workflow in plan.workflows:
        sections.append(f"### {workflow.requirement}\n{workflow.implementation}")
        for port_id in workflow.port_ids:
            port = ports[port_id]
            sections.append(
                f"- `{port_id}`: `{port['method']} {port['path']}`; auth: `{port['auth']}`"
            )
        sections.extend(f"- Acceptance: {check}" for check in workflow.acceptance_checks)
        sections.extend(f"- Unsupported / unresolved: {gap}" for gap in workflow.gaps)
    sections.append("### Resource declarations\n" + "\n".join(plan.provisioning))
    sections.append("### Owner setup / prerequisites\n" + "\n".join(plan.setup_requirements))
    return {
        **result.model_dump(),
        "specification": "\n\n".join(sections),
        "build_plan": plan.model_dump(),
        "cloud_contract": contract,
        "cloud_contract_sha256": fingerprint,
    }
