"""Small authenticated API used to validate platform connectivity."""

from __future__ import annotations

from datetime import datetime, timezone
import uuid

from flask import Blueprint, current_app, g, jsonify, request, send_file
from sqlalchemy import select
from sqlalchemy.exc import SQLAlchemyError

from bridge_platform.auth.jwt_auth import (
    AuthenticationError,
    authenticate_request,
    read_bootstrap_public_key,
    require_scope,
)
from bridge_platform.sites.registry import (
    describe_site,
    register_bootstrap_site,
    revoke_signing_key,
    rotate_signing_key,
)
from bridge_platform.entitlements.service import get_effective_entitlements
from bridge_platform.quotas.service import usage_snapshot
from bridge_platform.storage.service import get_storage_snapshot
from bridge_platform.billing.webhooks import BillingWebhookError, process_event, verify_signature
from bridge_platform.billing.stripe_adapter import (
    stripe_readiness,
    translate_stripe_event,
    verify_stripe_signature,
)
from bridge_platform.entitlements.service import subscription_allows_app
from bridge_platform.apps.registry import AppContractError, load_manifest
from bridge_platform.agents.tool_catalog import manifest_tools
from bridge_platform.agents.runtime import AgentResolutionError, resolve_instruction
from bridge_platform.agents.providers import OpenAIToolSelector
from bridge_platform.agents.profiles import filter_profile_tools, get_profile, list_profiles
from bridge_platform.logging.platform_logger import get_platform_logger, log_structured
from bridge_platform.secrets.encryption import SecretConfigurationError, SecretIntegrityError
from bridge_platform.secrets.secrets_manager import SecretsManager
from config.control_plane import get_control_plane_session
from bridge_platform.tenants.models import WordPressBridgeRelease, WordPressPackageRelease


platform_api = Blueprint("platform_api", __name__, url_prefix="/api/v1/platform")
AGENT_LOGGER = get_platform_logger("agent_runtime")


@platform_api.get("/agent/profiles")
def agent_profiles():
    try:
        identity = authenticate_request(request)
        require_scope(identity, "platform:agent:tools:read")
        profiles = list_profiles(identity.tenant_id, identity.roles)
    except AuthenticationError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 401
    except PermissionError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 403
    return jsonify({"status": "ok", "profiles": [profile.to_dict() for profile in profiles]})


@platform_api.get("/agent/status")
def agent_runtime_status():
    try:
        identity = authenticate_request(request)
        require_scope(identity, "platform:agent:tools:read")
        selector = OpenAIToolSelector.from_environment()
    except AuthenticationError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 401
    except PermissionError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 403
    return jsonify({
        "status": "ok",
        "runtime": {
            "mode": "hybrid",
            "deterministic_enabled": True,
            "llm_enabled": selector is not None,
            "llm_provider": "openai" if selector is not None else None,
            "llm_model": selector.model if selector is not None else None,
            "execution_policy": "gateway_validated",
        },
    })


@platform_api.post("/agent/resolve")
def resolve_agent_instruction():
    """Resolve a basic instruction to a permitted tool call; execution stays on the app gateway."""
    try:
        identity = authenticate_request(request)
        require_scope(identity, "platform:agent:invoke")
        payload = request.get_json(silent=True) or {}
        if not isinstance(payload, dict) or not isinstance(payload.get("message"), str):
            raise AgentResolutionError("invalid_request", "message must be a string")
        agent_key = str(payload.get("agent_key") or "abn_assistant").strip()
        profile = get_profile(identity.tenant_id, agent_key, identity.roles)
        tools = _tenant_agent_tools(
            identity.tenant_id, compact=False, agent_key=agent_key, roles=identity.roles,
        )
        request_id = str(uuid.uuid4())
        selection = resolve_instruction(
            payload["message"], tools,
            context={
                "tenant_id": identity.tenant_id,
                "site_id": identity.site_id,
                "user_id": identity.subject,
                "request_id": request_id,
                "agent_key": profile.agent_key,
            },
            provider=OpenAIToolSelector.from_environment(),
        )
        log_structured(
            AGENT_LOGGER,
            "agent_instruction_resolved",
            tenant_id=identity.tenant_id,
            site_id=identity.site_id,
            user_id=identity.subject,
            capability=selection["capability"],
            agent_key=profile.agent_key,
            resolver=selection["resolver"],
            request_id=request_id,
        )
    except AuthenticationError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 401
    except PermissionError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 403
    except AgentResolutionError as exc:
        return jsonify({
            "status": "error", "error": {"code": exc.code, "message": str(exc)}
        }), 422
    selection["agent_key"] = profile.agent_key
    selection["profile_version"] = profile.version
    selection["max_steps"] = profile.max_steps
    return jsonify({"status": "ok", "request_id": request_id, "selection": selection})


@platform_api.get("/agent/tools")
def agent_tools():
    """Return only agent-exposed tools from apps enabled for this tenant."""
    try:
        identity = authenticate_request(request)
        require_scope(identity, "platform:agent:tools:read")
        capability = request.args.get("capability", "").strip()
        agent_key = request.args.get("agent_key", "").strip()
        tools = _tenant_agent_tools(
            identity.tenant_id, compact=not bool(capability),
            agent_key=agent_key or None, roles=identity.roles,
        )
        if capability:
            matches = [tool for tool in tools if tool["capability"] == capability]
            if not matches:
                raise LookupError("Agent capability not found or not available")
            return jsonify({"status": "ok", "tool": matches[0]})
    except AuthenticationError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 401
    except PermissionError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 403
    except LookupError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 404
    return jsonify({"status": "ok", "tools": tools})


def _tenant_agent_tools(
    tenant_id: str,
    *,
    compact: bool,
    agent_key: str | None = None,
    roles: tuple[str, ...] = (),
) -> list[dict]:
    entitlements = get_effective_entitlements(tenant_id)
    tools: list[dict] = []
    for app in entitlements.get("apps", []):
        if not app.get("enabled"):
            continue
        try:
            manifest = load_manifest(str(app.get("app_id", "")))
        except (AppContractError, ImportError, ModuleNotFoundError):
            continue
        if manifest is not None:
            tools.extend(manifest_tools(manifest, compact=compact))
    if agent_key:
        tools = filter_profile_tools(get_profile(tenant_id, agent_key, roles), tools)
    return sorted(tools, key=lambda tool: tool["capability"])


@platform_api.get("/bridge/releases")
def wordpress_bridge_releases():
    try:
        identity = authenticate_request(request)
        require_scope(identity, "platform:bridge:updates:read")
        channel = request.args.get("channel", "stable")
        if channel not in {"development", "beta", "stable", "security"}:
            raise ValueError("Invalid release channel")
        with get_control_plane_session() as session:
            releases = session.execute(select(WordPressBridgeRelease).where(
                WordPressBridgeRelease.channel == channel,
                WordPressBridgeRelease.status.in_(("test", "published", "revoked")),
            ).order_by(WordPressBridgeRelease.version)).scalars().all()
            payload = [_bridge_release_payload(release) for release in releases]
    except AuthenticationError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 401
    except PermissionError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 403
    except ValueError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 400
    return jsonify({"status": "ok", "channel": channel, "releases": payload})


@platform_api.get("/bridge/releases/<version>/download")
def download_wordpress_bridge(version: str):
    try:
        identity = authenticate_request(request)
        require_scope(identity, "platform:bridge:updates:download")
        with get_control_plane_session() as session:
            release = session.execute(select(WordPressBridgeRelease).where(
                WordPressBridgeRelease.version == version,
                WordPressBridgeRelease.status.in_(("test", "published")),
                WordPressBridgeRelease.revoked_at.is_(None),
            )).scalar_one_or_none()
            if release is None:
                raise LookupError("Bridge release not found")
            artifact_path, artifact_name = release.artifact_path, release.artifact_name
    except AuthenticationError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 401
    except PermissionError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 403
    except LookupError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 404
    return send_file(artifact_path, mimetype="application/zip", as_attachment=True,
                     download_name=artifact_name, conditional=True)


def _bridge_release_payload(release: WordPressBridgeRelease) -> dict:
    metadata = release.metadata_json or {}
    revoked = release.revoked_at is not None or release.status == "revoked"
    return {
        "version": release.version, "channel": release.channel,
        "artifact_name": release.artifact_name, "artifact_size": release.artifact_size,
        "sha256": release.sha256, "signature": release.signature,
        "signature_algorithm": release.signature_algorithm,
        "minimum_wordpress_version": metadata.get("minimum_wordpress_version"),
        "minimum_php_version": metadata.get("minimum_php_version"),
        "schema_version": metadata.get("schema_version", 1),
        "security_update": bool(metadata.get("security_update")),
        "release_notes": metadata.get("release_notes", []),
        "revoked": revoked, "revoked_reason": release.revoked_reason,
        "download_path": None if revoked else f"/api/v1/bridge_platform/bridge/releases/{release.version}/download",
    }


@platform_api.post("/billing/webhooks/<provider>")
def billing_webhook(provider: str):
    """Receive provider-adapter events without a site JWT."""
    raw_body = request.get_data(cache=True)
    try:
        payload = request.get_json(silent=False)
        if not isinstance(payload, dict):
            raise BillingWebhookError("Webhook payload must be a JSON object")
        if provider.lower() == "stripe":
            verify_stripe_signature(raw_body, request.headers.get("Stripe-Signature", ""))
            platform_payload = translate_stripe_event(payload)
        else:
            verify_signature(
                provider,
                raw_body,
                request.headers.get("X-Billing-Timestamp", ""),
                request.headers.get("X-Billing-Signature", ""),
            )
            platform_payload = payload
        result = process_event(provider.lower(), platform_payload, raw_body)
    except BillingWebhookError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 400
    except Exception:
        return jsonify({"status": "error", "message": "Invalid billing webhook"}), 400
    return jsonify({"status": "ok", **result})


@platform_api.get("/billing/status")
def billing_status():
    try:
        identity = authenticate_request(request)
        require_scope(identity, "platform:billing:read")
    except AuthenticationError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 401
    except PermissionError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 403
    return jsonify({"status": "ok", "billing": stripe_readiness()})


@platform_api.get("/integrations/<app_id>/credentials")
def integration_credentials_status(app_id: str):
    try:
        identity = authenticate_request(request)
        require_scope(identity, "platform:integrations:read")
        if not subscription_allows_app(identity.tenant_id, app_id):
            raise PermissionError(f"Subscription does not allow app: {app_id}")
        credentials = SecretsManager().list_secret_metadata(
            tenant_id=identity.tenant_id, app_id=app_id,
        )
    except AuthenticationError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 401
    except PermissionError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 403
    except SecretConfigurationError:
        return jsonify({"status": "error", "message": "Credential vault is not configured"}), 503
    return jsonify({"status": "ok", "app_id": app_id, "credentials": credentials})


@platform_api.put("/integrations/<app_id>/credentials")
def replace_integration_credentials(app_id: str):
    try:
        identity = authenticate_request(request)
        require_scope(identity, "platform:integrations:write")
        if not subscription_allows_app(identity.tenant_id, app_id):
            raise PermissionError(f"Subscription does not allow app: {app_id}")
        payload = request.get_json(silent=True) or {}
        credentials = payload.get("credentials")
        if not isinstance(credentials, dict) or not credentials or len(credentials) > 20:
            raise ValueError("credentials must be a non-empty object with at most 20 entries")
        manager = SecretsManager()
        configured = []
        for secret_name, secret_value in credentials.items():
            manager.put_secret(
                tenant_id=identity.tenant_id,
                app_id=app_id,
                secret_name=str(secret_name),
                secret_value=str(secret_value),
                actor_id=identity.subject,
            )
            configured.append(str(secret_name))
    except AuthenticationError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 401
    except PermissionError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 403
    except (ValueError, SecretIntegrityError) as exc:
        return jsonify({"status": "error", "message": str(exc)}), 400
    except LookupError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 404
    except SecretConfigurationError:
        return jsonify({"status": "error", "message": "Credential vault is not configured"}), 503
    return jsonify({
        "status": "ok",
        "app_id": app_id,
        "configured": sorted(configured),
        "values_returned": False,
    })


@platform_api.delete("/integrations/<app_id>/credentials/<secret_name>")
def delete_integration_credential(app_id: str, secret_name: str):
    try:
        identity = authenticate_request(request)
        require_scope(identity, "platform:integrations:write")
        if not subscription_allows_app(identity.tenant_id, app_id):
            raise PermissionError(f"Subscription does not allow app: {app_id}")
        deleted = SecretsManager().delete_secret(
            tenant_id=identity.tenant_id, app_id=app_id, secret_name=secret_name,
        )
    except AuthenticationError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 401
    except PermissionError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 403
    except SecretConfigurationError:
        return jsonify({"status": "error", "message": "Credential vault is not configured"}), 503
    return jsonify({"status": "ok", "deleted": deleted, "values_returned": False})


@platform_api.get("/packages")
def wordpress_package_catalog():
    try:
        identity = authenticate_request(request)
        require_scope(identity, "platform:packages:read")
        with get_control_plane_session() as session:
            releases = session.execute(
                select(WordPressPackageRelease).where(
                    WordPressPackageRelease.status.in_(("test", "published", "revoked")),
                ).order_by(WordPressPackageRelease.app_id, WordPressPackageRelease.version)
            ).scalars().all()
            packages = [
                _package_payload(release)
                for release in releases
                if subscription_allows_app(identity.tenant_id, release.app_id)
            ]
    except AuthenticationError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 401
    except PermissionError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 403
    return jsonify({"status": "ok", "packages": packages})


@platform_api.get("/packages/<package_id>/<version>/download")
def download_wordpress_package(package_id: str, version: str):
    try:
        identity = authenticate_request(request)
        require_scope(identity, "platform:packages:download")
        with get_control_plane_session() as session:
            release = session.execute(select(WordPressPackageRelease).where(
                WordPressPackageRelease.package_id == package_id,
                WordPressPackageRelease.version == version,
                WordPressPackageRelease.status.in_(("test", "published")),
                WordPressPackageRelease.revoked_at.is_(None),
            )).scalar_one_or_none()
            if release is None:
                raise LookupError("Package release not found")
            if not subscription_allows_app(identity.tenant_id, release.app_id):
                raise PermissionError("Subscription does not allow this package")
            artifact_path, artifact_name = release.artifact_path, release.artifact_name
    except AuthenticationError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 401
    except PermissionError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 403
    except LookupError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 404
    return send_file(
        artifact_path, mimetype="application/zip", as_attachment=True,
        download_name=artifact_name, conditional=True,
    )


def _package_payload(release: WordPressPackageRelease) -> dict:
    compatibility = release.compatibility_json or {}
    return {
        "package_id": release.package_id,
        "app_id": release.app_id,
        "version": release.version,
        "status": release.status,
        "artifact_name": release.artifact_name,
        "artifact_size": release.artifact_size,
        "sha256": release.sha256,
        "signature": release.signature,
        "signature_algorithm": release.signature_algorithm,
        "compatibility": {
            "bridge": compatibility.get("bridge"),
            "wordpress": compatibility.get("wordpress"),
            "php": compatibility.get("php"),
        },
        "release_notes": compatibility.get("release_notes", []),
        "plugin_file": compatibility.get("plugin_file"),
        "revoked": release.revoked_at is not None or release.status == "revoked",
        "revoked_reason": release.revoked_reason,
        "capabilities": release.capabilities_json or [],
        "download_path": None if release.revoked_at is not None or release.status == "revoked" else (
            f"/api/v1/bridge_platform/packages/{release.package_id}/{release.version}/download"
        ),
    }


@platform_api.get("/connection")
def connection_test():
    """Return safe request and identity metadata after full JWT validation."""
    try:
        identity = authenticate_request(request)
        require_scope(identity, "platform:connect")
    except AuthenticationError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 401
    except PermissionError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 403

    g.identity = identity
    if (
        identity.key_source == "bootstrap_file"
        and current_app.config.get("REGISTER_BOOTSTRAP_SITES", True)
    ):
        try:
            register_bootstrap_site(
                site_id=identity.site_id,
                tenant_id=identity.tenant_id,
                issuer=identity.issuer,
                audience=str(identity.claims["aud"]),
                key_id=identity.key_id,
                public_key_pem=read_bootstrap_public_key(identity.key_id),
            )
        except (LookupError, PermissionError, ValueError) as exc:
            return jsonify({"status": "error", "message": str(exc)}), 403
        except SQLAlchemyError:
            return jsonify({"status": "error", "message": "Control plane unavailable"}), 503

    return jsonify(
        {
            "status": "ok",
            "service": "flask_server",
            "authenticated": True,
            "server_time": datetime.now(timezone.utc).isoformat(),
            "identity": {
                "tenant_id": identity.tenant_id,
                "site_id": identity.site_id,
                "subject": identity.subject,
                "issuer": identity.issuer,
                "kid": identity.key_id,
                "key_source": identity.key_source,
                "roles": list(identity.roles),
                "scopes": list(identity.scopes),
            },
            "request": {
                "method": request.method,
                "path": request.path,
                "request_id": identity.claims.get("jti"),
            },
        }
    )


@platform_api.get("/site")
def site_status():
    try:
        identity = authenticate_request(request)
        require_scope(identity, "platform:connect")
    except AuthenticationError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 401
    except PermissionError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 403

    site = describe_site(identity.site_id)
    if site is None:
        return jsonify({"status": "error", "message": "Site is not registered"}), 404
    return jsonify({"status": "ok", "site": site})


@platform_api.get("/entitlements")
def effective_entitlements():
    try:
        identity = authenticate_request(request)
        require_scope(identity, "platform:entitlements:read")
        entitlements = get_effective_entitlements(identity.tenant_id)
    except AuthenticationError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 401
    except PermissionError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 403
    except LookupError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 404
    return jsonify({"status": "ok", "entitlements": entitlements})


@platform_api.get("/usage")
def effective_usage():
    try:
        identity = authenticate_request(request)
        require_scope(identity, "platform:usage:read")
        usage = usage_snapshot(identity.tenant_id)
        usage["storage_mb"] = get_storage_snapshot(identity.tenant_id).to_dict()
    except AuthenticationError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 401
    except PermissionError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 403
    return jsonify({"status": "ok", "tenant_id": identity.tenant_id, "usage": usage})


@platform_api.post("/site/keys/rotate")
def rotate_site_key():
    try:
        identity = authenticate_request(request)
        require_scope(identity, "platform:keys:rotate")
        payload = request.get_json(silent=True) or {}
        new_kid = str(payload.get("kid") or "").strip()
        public_key = str(payload.get("public_key") or "").strip()
        if not new_kid or not public_key:
            raise ValueError("kid and public_key are required")
        rotate_signing_key(
            site_id=identity.site_id,
            current_key_id=identity.key_id,
            new_key_id=new_kid,
            public_key_pem=public_key,
        )
    except AuthenticationError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 401
    except PermissionError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 403
    except LookupError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 404
    except ValueError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 400
    return jsonify({"status": "ok", "kid": new_kid, "message": "Signing key rotated"})


@platform_api.post("/site/keys/<key_id>/revoke")
def revoke_site_key(key_id: str):
    try:
        identity = authenticate_request(request)
        require_scope(identity, "platform:keys:revoke")
        revoke_signing_key(
            site_id=identity.site_id,
            key_id=key_id,
            current_key_id=identity.key_id,
        )
    except AuthenticationError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 401
    except PermissionError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 403
    except LookupError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 404
    except ValueError as exc:
        return jsonify({"status": "error", "message": str(exc)}), 400
    return jsonify({"status": "ok", "kid": key_id, "message": "Signing key revoked"})
