"""FastAPI application entry point."""
from contextlib import asynccontextmanager
from pathlib import Path

import structlog
from fastapi import FastAPI, Request
from fastapi.exception_handlers import request_validation_exception_handler
from fastapi.exceptions import RequestValidationError
from fastapi.middleware.cors import CORSMiddleware
from fastapi.openapi.utils import get_openapi
from fastapi.responses import JSONResponse, Response

from src.config import get_settings
from src.core.agent_auth import AUTH_MD_PATH, build_auth_md
from src.core.api_catalog import (
    AGENT_OPERATION_PATHS,
    LINKSET_MEDIA_TYPE,
    PUBLIC_OPERATION_PATHS,
    build_api_catalog,
    catalog_link_header,
)
from src.core.exceptions import P2HSError, to_http_exception
from src.core.middleware import (
    LoggingMiddleware,
    RequestIDMiddleware,
)
from src.core.process_identity import PROCESS_GIT_SHA
from src.core.redis import close_redis

# DCR is a public endpoint that only ever accepts OAuth client metadata — no
# user secrets — so it's safe to log the exact validation failure here. This is
# deliberately scoped to that one path: RequestValidationError fires for every
# route in the app, and most bodies (signup, billing, etc.) can contain values
# that must never be written to logs.
LOGGABLE_VALIDATION_PATHS = frozenset({"/api/agent-access/clients"})


@asynccontextmanager
async def lifespan(app: FastAPI):
    """Application lifespan - startup and shutdown."""
    settings = get_settings()
    if settings.startup_diagnostics:
        try:
            from src.core.diagnostics import run_and_log_warnings
            await run_and_log_warnings()
        except Exception as exc:
            import logging
            logging.getLogger(__name__).warning("diagnostics_startup_error: %s", exc)

    from src.domain.storage_ingest.background import (
        start_storage_ingest_background_tasks,
        stop_storage_ingest_background_tasks,
    )
    start_storage_ingest_background_tasks()

    yield
    await stop_storage_ingest_background_tasks()
    await close_redis()


def create_app() -> FastAPI:
    """Create and configure FastAPI application."""
    settings = get_settings()

    app = FastAPI(
        title="P2HS API",
        description="AI-driven website/app builder and deployment platform",
        version="0.1.0",
        docs_url="/docs",
        redoc_url="/redoc" if settings.is_dev else None,
        lifespan=lifespan,
    )

    app.add_middleware(RequestIDMiddleware)
    app.add_middleware(LoggingMiddleware)
    app.add_middleware(
        CORSMiddleware,
        allow_origins=["*"] if settings.is_dev else [],
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )

    @app.exception_handler(P2HSError)
    async def p2hs_exception_handler(request: Request, exc: P2HSError):
        http_exc = to_http_exception(exc)
        return JSONResponse(
            status_code=http_exc.status_code,
            content={"detail": http_exc.detail},
        )

    @app.exception_handler(RequestValidationError)
    async def validation_exception_handler(request: Request, exc: RequestValidationError):
        if request.url.path in LOGGABLE_VALIDATION_PATHS:
            structlog.get_logger().info(
                "request_validation_failed", path=request.url.path, errors=exc.errors()
            )
        return await request_validation_exception_handler(request, exc)

    @app.exception_handler(Exception)
    async def unhandled_exception_handler(request: Request, exc: Exception):
        import logging
        import traceback
        logging.getLogger(__name__).error(
            "unhandled_exception path=%s method=%s error=%r\n%s",
            request.url.path,
            request.method,
            exc,
            traceback.format_exc(),
        )
        return JSONResponse(
            status_code=500,
            content={"detail": {"error": "internal_error", "message": "An unexpected error occurred."}},
        )

    @app.api_route("/api/discovery/health", methods=["GET", "HEAD"], include_in_schema=False)
    @app.get("/health")
    async def health():
        return {"status": "ok", "git_sha": PROCESS_GIT_SHA}

    # Ingress may forward only /api/* to FastAPI. Dashboard keeps the canonical
    # public discovery URLs while fetching these aliases through that ingress.
    @app.api_route("/api/discovery/public-openapi.json", methods=["GET", "HEAD"], include_in_schema=False)
    @app.api_route("/public-openapi.json", methods=["GET", "HEAD"], include_in_schema=False)
    async def public_openapi(request: Request):
        """Public dashboard contract, excluding internal/admin operations and schemas."""
        if request.method == "HEAD":
            return Response(media_type="application/json")
        document = get_openapi(
            title="P2HS Public API",
            version=app.version,
            routes=[r for r in app.routes if getattr(r, "path", None) in (PUBLIC_OPERATION_PATHS | (AGENT_OPERATION_PATHS if settings.agent_access_enabled else frozenset()))],
            servers=[{"url": settings.app_url.rstrip("/")}],
        )
        return JSONResponse(document)

    @app.api_route("/api/discovery/api-catalog", methods=["GET", "HEAD"], include_in_schema=False)
    @app.api_route("/.well-known/api-catalog", methods=["GET", "HEAD"])
    async def api_catalog(request: Request):
        """RFC 9727 API catalog for automated discovery."""
        origin = settings.app_url.rstrip("/")
        headers = {
            "Link": catalog_link_header(origin),
            "Cache-Control": "public, max-age=300",
        }
        if request.method == "HEAD":
            return Response(status_code=200, media_type=LINKSET_MEDIA_TYPE, headers=headers)
        return JSONResponse(
            content=build_api_catalog(origin, agent_enabled=settings.agent_access_enabled),
            media_type=LINKSET_MEDIA_TYPE,
            headers=headers,
        )

    discovery_headers = {"Cache-Control": "public, max-age=300"}

    @app.api_route("/api/discovery/auth.md", methods=["GET", "HEAD"], include_in_schema=False)
    @app.api_route(AUTH_MD_PATH, methods=["GET", "HEAD"])
    async def auth_md(request: Request):
        """Auth.md skill: how agents register against this service."""
        origin = settings.app_url.rstrip("/")
        body = build_auth_md(origin, home_url=settings.home_url, agent_enabled=settings.agent_access_enabled, mcp_url=settings.agent_mcp_url, a2a_url=settings.agent_a2a_url)
        headers = dict(discovery_headers)
        media_type = "text/markdown; charset=utf-8"
        if request.method == "HEAD":
            return Response(status_code=200, media_type=media_type, headers=headers)
        return Response(content=body, media_type=media_type, headers=headers)

    # OIDC/JWKS are not published — not implemented. A2A's agent-card.json and OAuth's
    # oauth-authorization-server live in src/domain/agent_access/discovery.py, gated the
    # same way (agent_access_enabled + X-P2HS-Agent-Proxy), and only reachable through
    # Dashboard's public-backend-proxy allowlist.

    _ve_script_path = Path(__file__).parent.parent / "boilerplates" / "react_app" / "src" / "p2hs-visual-edit.js"

    @app.get("/p2hs-visual-edit.js")
    async def visual_edit_script():
        content = _ve_script_path.read_text(encoding="utf-8")
        return Response(content=content, media_type="application/javascript")

    @app.get("/health/integrations")
    async def health_integrations():
        """Check all external integration configurations (GitHub App, CF, Stripe, Redis, DB)."""
        from src.core.diagnostics import run_all
        report = await run_all()
        return report.to_dict()

    from src.api.ws import router as ws_router
    from src.domain.admin.auth_router import router as admin_auth_router
    from src.domain.admin.management_router import router as admin_management_router
    from src.domain.admin.public_router import router as plans_router
    from src.domain.admin.router import router as admin_router
    from src.domain.agent_access.discovery import router as agent_discovery_router
    from src.domain.agent_access.router import router as agent_access_router
    from src.domain.ai.router import router as ai_router
    from src.domain.ai.router import workspace_router as ai_workspace_router
    from src.domain.api_keys.router import router as api_keys_router
    from src.domain.artifacts.router import router as artifacts_router
    from src.domain.auth.router import router as auth_router
    from src.domain.auth.users_router import router as users_router
    from src.domain.azure_user_mirror.router import router as azure_user_mirror_router
    from src.domain.billing.router import router as billing_router
    from src.domain.bitbucket_user_mirror.router import router as bitbucket_user_mirror_router
    from src.domain.builder.router import router as builder_router
    from src.domain.capabilities.router import router as capability_lifecycle_router
    from src.domain.capabilities.operator_router import router as capability_operator_router
    from src.domain.cloud.capabilities_router import router as cloud_capabilities_router
    from src.domain.cloud.jobs_router import router as project_jobs_router
    from src.domain.cloud.operate_router import router as project_operate_router
    from src.domain.cloud.router import router as cloud_internal_router
    from src.domain.cloud_credits.router import router as cloud_credits_router
    from src.domain.cloud_infra.router import admin_router as cloud_infra_admin_router
    from src.domain.cloud_infra.router import internal_router as cloud_infra_internal_router
    from src.domain.codegen.provider_router import admin_router as codegen_provider_admin_router
    from src.domain.codegen.provider_router import project_router as codegen_provider_project_router
    from src.domain.codegen.provider_router import (
        workspace_router as codegen_provider_workspace_router,
    )
    from src.domain.codegen.router import internal_router as codegen_internal_router
    from src.domain.codegen.router import router as codegen_router
    from src.domain.connectors.router import capability_router as connectors_capability_router
    from src.domain.connectors.router import catalog_router as connectors_catalog_router
    from src.domain.connectors.router import project_router as connectors_project_router
    from src.domain.connectors.router import (
        workspace_defaults_router as connectors_workspace_defaults_router,
    )
    from src.domain.deployments.router import router as deployments_router
    from src.domain.domains.router import router as domains_router
    from src.domain.desktop_auth.router import router as desktop_auth_router
    from src.domain.devices.router import router as devices_router
    from src.domain.devices.router import workspace_router as devices_workspace_router
    from src.domain.discord_community.router import router as discord_community_router
    from src.domain.dropbox_imports.router import router as dropbox_imports_router
    from src.domain.edge_functions.router import admin_function_library_router
    from src.domain.edge_functions.router import functions_router as edge_functions_router
    from src.domain.edge_functions.router import library_router as fn_library_router
    from src.domain.edge_functions.router import runtime_router as runtime_versions_router
    from src.domain.edge_functions.router import webhooks_router as edge_webhooks_router
    from src.domain.email_domains.router import router as email_domains_router
    from src.domain.external_admin.router import plugin_router as external_admin_plugin_router
    from src.domain.external_admin.router import router as external_admin_router
    from src.domain.entity_ingest.router import entity_ingest_router
    from src.domain.external_admin.router import workspace_router as external_admin_workspace_router
    from src.domain.external_connections.router import router as external_connections_router
    from src.domain.feature_packs.router import router as feature_packs_router
    from src.domain.fleet.router import admin_router as fleet_admin_router
    from src.domain.fleet.router import internal_router as fleet_internal_router
    from src.domain.forms.router import forms_router
    from src.domain.forms.admin_router import router as forms_admin_router
    from src.domain.github_user_mirror.router import router as github_user_mirror_router
    from src.domain.github_webhooks.router import router as github_webhooks_router
    from src.domain.gitlab_user_mirror.router import router as gitlab_user_mirror_router
    from src.domain.guest_drafts.router import router as guest_drafts_router
    from src.domain.hosting.router import router as hosting_admin_router
    from src.domain.instructions.router import router as instructions_router
    from src.domain.integration_events.router import command_router as integration_command_router
    from src.domain.integration_events.router import project_router as event_subscriptions_router
    from src.domain.integration_events.router import registry_router as event_registry_router
    from src.domain.integrations.router import catalog_router as integrations_catalog_router
    from src.domain.integrations.router import router as integrations_router
    from src.domain.l3_registry.router import internal_router as l3_internal_router
    from src.domain.l3_registry.router import router as l3_router
    from src.domain.livechat.router import router as livechat_router
    from src.domain.logs.router import router as logs_router
    from src.domain.manifest.router import router as manifest_router
    from src.domain.previews.router import router as previews_router
    from src.domain.project_auth.router import router as project_auth_router
    from src.domain.project_auth.membership_mutations_router import router as membership_mutations_router
    from src.domain.project_imports.router import router as project_imports_router
    from src.domain.project_storage.router import router as project_storage_router
    from src.domain.projects.router import router as projects_router
    from src.domain.prompt_media.router import router as prompt_media_router
    from src.domain.public.router import router as public_signup_router
    from src.domain.realtime.router import router as realtime_router
    from src.domain.reference_data.router import router as reference_data_router
    from src.domain.revisions.router import router as revisions_router
    from src.domain.runtime_ai.router import router as runtime_ai_router
    from src.domain.shopify_apps.router import router as shopify_apps_router
    from src.domain.storage_ingest.router import router as storage_ingest_router
    from src.domain.templates.router import admin_router as templates_admin_router
    from src.domain.templates.router import public_router as templates_public_router
    from src.domain.prompt_suggestions.router import admin_router as prompt_suggestions_admin_router
    from src.domain.prompt_suggestions.router import public_router as prompt_suggestions_public_router
    from src.domain.composition_recipes.router import router as composition_recipes_router
    from src.domain.usage.router import cloud_router as usage_cloud_router
    from src.domain.usage.router import project_router as usage_project_router
    from src.domain.usage.router import workspace_router as usage_workspace_router
    from src.domain.voice.router import router as voice_router
    from src.domain.workspaces.router import router as workspaces_router
    app.include_router(agent_discovery_router)
    app.include_router(agent_access_router)
    app.include_router(auth_router)
    app.include_router(users_router)
    app.include_router(workspaces_router)
    app.include_router(projects_router)
    app.include_router(builder_router)
    app.include_router(prompt_media_router)
    app.include_router(guest_drafts_router)
    app.include_router(instructions_router)
    app.include_router(revisions_router)
    app.include_router(codegen_router)
    app.include_router(l3_internal_router)
    app.include_router(l3_router)
    app.include_router(codegen_internal_router)
    app.include_router(devices_router)
    app.include_router(devices_workspace_router)
    app.include_router(desktop_auth_router)
    app.include_router(discord_community_router)
    app.include_router(previews_router)
    app.include_router(deployments_router)
    app.include_router(domains_router)
    app.include_router(email_domains_router)
    app.include_router(billing_router)
    app.include_router(api_keys_router)
    app.include_router(artifacts_router)
    app.include_router(github_webhooks_router)
    app.include_router(github_user_mirror_router)
    app.include_router(gitlab_user_mirror_router)
    app.include_router(bitbucket_user_mirror_router)
    app.include_router(azure_user_mirror_router)
    app.include_router(project_imports_router)
    app.include_router(admin_router)
    app.include_router(admin_auth_router)
    app.include_router(admin_management_router)
    app.include_router(codegen_provider_admin_router)
    app.include_router(codegen_provider_workspace_router)
    app.include_router(codegen_provider_project_router)
    app.include_router(hosting_admin_router)
    from src.domain.hosting.w4p_activation import router as w4p_activation_router
    app.include_router(w4p_activation_router)
    app.include_router(plans_router)
    app.include_router(ws_router)
    app.include_router(cloud_internal_router)
    app.include_router(cloud_infra_admin_router)
    app.include_router(cloud_infra_internal_router)
    app.include_router(project_auth_router)
    app.include_router(membership_mutations_router)
    app.include_router(project_storage_router)
    app.include_router(storage_ingest_router)
    app.include_router(dropbox_imports_router)
    app.include_router(project_jobs_router)
    app.include_router(project_operate_router)
    app.include_router(fn_library_router)
    app.include_router(admin_function_library_router)
    app.include_router(edge_functions_router)
    app.include_router(edge_webhooks_router)
    app.include_router(runtime_versions_router)
    app.include_router(external_connections_router)
    app.include_router(feature_packs_router)
    app.include_router(realtime_router)
    app.include_router(reference_data_router)
    app.include_router(voice_router)
    app.include_router(livechat_router)
    app.include_router(cloud_capabilities_router)
    app.include_router(runtime_ai_router)
    app.include_router(cloud_credits_router)
    app.include_router(ai_router)
    app.include_router(ai_workspace_router)
    app.include_router(fleet_admin_router)
    app.include_router(fleet_internal_router)
    app.include_router(logs_router)
    app.include_router(usage_project_router)
    app.include_router(usage_workspace_router)
    app.include_router(usage_cloud_router)
    app.include_router(connectors_catalog_router)
    from src.domain.provider_auth.router import router as provider_auth_router
    app.include_router(provider_auth_router)
    app.include_router(connectors_workspace_defaults_router)
    app.include_router(connectors_project_router)
    app.include_router(connectors_capability_router)
    app.include_router(forms_router)
    app.include_router(forms_admin_router)
    app.include_router(capability_lifecycle_router)
    app.include_router(capability_operator_router)
    app.include_router(entity_ingest_router)
    app.include_router(event_registry_router)
    app.include_router(event_subscriptions_router)
    app.include_router(integration_command_router)
    app.include_router(integrations_catalog_router)
    app.include_router(integrations_router)
    app.include_router(templates_admin_router)
    app.include_router(templates_public_router)
    app.include_router(prompt_suggestions_admin_router)
    app.include_router(prompt_suggestions_public_router)
    app.include_router(composition_recipes_router)
    app.include_router(manifest_router)
    app.include_router(shopify_apps_router)
    app.include_router(external_admin_router)
    app.include_router(external_admin_workspace_router)
    app.include_router(external_admin_plugin_router)
    app.include_router(public_signup_router)

    return app


app = create_app()


def run():
    """Run the application (for p2hs-api CLI).

    In dev, reload only watches ``src/`` (not the whole CWD) so log files and other tree churn
    under the deploy root do not constantly respawn the worker.
    """
    import uvicorn

    from src.config import get_settings

    settings = get_settings()
    project_root = Path(__file__).resolve().parent.parent
    use_reload = settings.is_dev and settings.uvicorn_reload

    run_kw: dict = {
        "host": "0.0.0.0",
        "port": settings.port,
        "reload": use_reload,
    }
    if use_reload:
        run_kw["reload_dirs"] = [str(project_root / "src")]
        run_kw["reload_delay"] = 0.5
        run_kw["reload_excludes"] = [
            "**/__pycache__/**",
            "**/*.pyc",
            "**/*.log",
            "**/.pytest_cache/**",
        ]

    uvicorn.run("src.main:app", **run_kw)


if __name__ == "__main__":
    run()
