"""Platform App Standard v1 entrypoint for the AroFlo connector."""

from __future__ import annotations

from datetime import datetime, timezone
import sys

loaded_platform = sys.modules.get("platform")
if loaded_platform is not None and not hasattr(loaded_platform, "__path__"):
    del sys.modules["platform"]

from bridge_platform.apps.contracts import (
    ActionContract,
    AgentCapabilityContract,
    AppManifest,
    CredentialField,
    WordPressPackageContract,
)
from bridge_platform.apps.envelopes import error_envelope, success_envelope
from bridge_platform.secrets.encryption import SecretConfigurationError, SecretIntegrityError
from bridge_platform.secrets.secrets_manager import SecretsManager

from .client import AroFloClient, AroFloError
from .config import AroFloConfigError, AroFloSettings
from .services.capabilities import build_capabilities_manifest
from .bot_pool import bot_pool_summary, configure_bot, list_bots
from .worker_client import AroFloWorkerClient, WorkerError
from .services.users_v1 import UsersContractError, query_users


API_CREDENTIAL_NAMES = (
    "aroflo_base_url",
    "aroflo_u_encoded",
    "aroflo_p_encoded",
    "aroflo_api_secret",
    "aroflo_org_encoded",
)


APP_MANIFEST = AppManifest(
    app_id="aroflo_connector_app",
    display_name="AroFlo Connector",
    app_version="1.5.0-dev",
    description="Connect the platform to AroFlo through API-first and controlled UI transports.",
    credentials=(
        CredentialField(
            "aroflo_base_url",
            "AroFlo API base URL",
            kind="url",
            help_text="The tenant AroFlo API endpoint.",
        ),
        CredentialField(
            "aroflo_u_encoded",
            "AroFlo encoded user",
            help_text="The API user value issued by AroFlo.",
        ),
        CredentialField(
            "aroflo_p_encoded",
            "AroFlo encoded password",
            help_text="The API password value issued by AroFlo.",
        ),
        CredentialField(
            "aroflo_api_secret",
            "AroFlo API secret",
            help_text="The HMAC signing secret issued by AroFlo.",
        ),
        CredentialField(
            "aroflo_org_encoded",
            "AroFlo encoded organization",
            help_text="The API organization value issued by AroFlo.",
        ),
        CredentialField(
            "aroflo_host_ip",
            "AroFlo API host IP",
            kind="text",
            required=False,
            help_text="Optional source IP included in the AroFlo signature.",
        ),
    ),
    actions={
        "health_v1": ActionContract(
            capability="platform.health",
            description="Report local connector and vault readiness without contacting AroFlo.",
            output_schema={
                "type": "object",
                "required": [
                    "healthy",
                    "vault_ready",
                    "api_credentials_configured",
                    "provider_checked",
                ],
            },
            errors=(),
        ),
        "test_connection_v1": ActionContract(
            capability="platform.credentials.test",
            description="Validate tenant API credentials with a minimal read-only AroFlo request.",
            input_schema={"type": "object", "additionalProperties": False},
            output_schema={
                "type": "object",
                "required": ["connected", "provider", "transport", "tested_at"],
            },
            required_credentials=API_CREDENTIAL_NAMES,
            timeout_seconds=20,
            data_classification="internal",
            errors=(
                "tenant_context_missing",
                "credential_not_configured",
                "credential_vault_unavailable",
                "provider_connection_failed",
            ),
        ),
        "configure_api_v1": ActionContract(
            capability="aroflo.api.configure",
            description="Store tenant-scoped AroFlo API credentials as write-only vault secrets.",
            input_schema={"type": "object", "additionalProperties": False},
            output_schema={"type": "object", "required": ["configured", "fields"]},
            side_effects=True,
            idempotent=True,
            timeout_seconds=10,
            data_classification="restricted",
            errors=("tenant_context_missing", "invalid_input", "credential_vault_unavailable"),
        ),
        "zones_v1": ActionContract(
            capability="aroflo.catalog.read",
            description="List registered AroFlo zones and operation metadata without executing them.",
            input_schema={"type": "object", "additionalProperties": False},
            output_schema={
                "type": "object",
                "required": ["zones", "zone_count", "operation_count", "provider_checked"],
            },
            timeout_seconds=5,
            data_classification="internal",
            errors=(),
        ),
        "bots_v1": ActionContract(
            capability="aroflo.bots.list",
            description="List safe tenant BOT profiles and assigned zones.",
            output_schema={"type": "object", "required": ["bots", "configured", "connected"]},
            data_classification="confidential",
            errors=("tenant_context_missing",),
        ),
        "configure_bot_v1": ActionContract(
            capability="aroflo.bot.configure",
            description="Create or update one tenant BOT profile and its write-only credentials.",
            input_schema={
                "type": "object",
                "additionalProperties": False,
                "required": ["bot_id", "label", "zones"],
            },
            output_schema={"type": "object", "required": ["bot"]},
            side_effects=True,
            idempotent=True,
            timeout_seconds=10,
            data_classification="restricted",
            errors=("tenant_context_missing", "invalid_input", "bot_configuration_failed"),
        ),
        "bot_status_v1": ActionContract(
            capability="aroflo.bot.status",
            description="Return the safe live session state for one tenant BOT.",
            input_schema={"type": "object", "required": ["bot_id"]},
            output_schema={"type": "object", "required": ["bot_id", "state"]},
            data_classification="confidential",
            errors=("invalid_input", "bot_not_found", "worker_unavailable"),
        ),
        "connect_bot_v1": ActionContract(
            capability="aroflo.bot.connect",
            description="Start or resume an authenticated AroFlo browser session for one BOT.",
            input_schema={"type": "object", "required": ["bot_id"]},
            output_schema={"type": "object", "required": ["bot_id", "state"]},
            side_effects=True, idempotent=True, timeout_seconds=90,
            data_classification="restricted",
            errors=("invalid_input", "bot_not_found", "bot_busy", "worker_unavailable"),
        ),
        "submit_bot_mfa_v1": ActionContract(
            capability="aroflo.bot.mfa.submit",
            description="Submit one ephemeral MFA challenge response to an active BOT session.",
            input_schema={"type": "object", "required": ["bot_id", "code"]},
            output_schema={"type": "object", "required": ["bot_id", "state"]},
            side_effects=True, idempotent=False, idempotency_key_required=True,
            timeout_seconds=90, data_classification="restricted",
            errors=("invalid_input", "bot_not_found", "bot_busy", "mfa_rejected", "worker_unavailable"),
        ),
        "disconnect_bot_v1": ActionContract(
            capability="aroflo.bot.disconnect",
            description="Close one BOT browser session while preserving encrypted reusable state.",
            input_schema={"type": "object", "required": ["bot_id"]},
            output_schema={"type": "object", "required": ["bot_id", "state"]},
            side_effects=True, idempotent=True, timeout_seconds=20,
            data_classification="restricted",
            errors=("invalid_input", "bot_not_found", "bot_busy", "worker_unavailable"),
        ),
        "users_list_v1": ActionContract(
            capability="aroflo.users.list",
            description="List normalized AroFlo users with allowlisted filters and pagination.",
            input_schema={"type": "object", "additionalProperties": False,
                "properties": {"filters": {"type": "array"}, "sort": {"type": "array"},
                    "pagination": {"type": "object"}, "transport": {"enum": ["auto", "api"]}}},
            output_schema={"type": "object", "required": ["outcome", "items", "pagination", "meta"]},
            required_credentials=API_CREDENTIAL_NAMES, timeout_seconds=30,
            data_classification="confidential",
            errors=("invalid_input", "unsupported_filter", "transport_unavailable",
                    "credential_not_configured", "provider_unavailable"),
            agent=AgentCapabilityContract(
                exposed=True, title="List AroFlo users",
                summary="List normalized AroFlo users using safe filters and pagination.",
                domains=("aroflo", "users"),
                use_when=("The user asks for AroFlo users or staff matching known criteria.",),
                do_not_use_when=("The request changes a user or concerns non-AroFlo identities.",),
                risk_category="read", risk_level="low", confirmation="never",
                required_permissions=("aroflo.users.read",),
            ),
        ),
        "users_get_v1": ActionContract(
            capability="aroflo.users.get",
            description="Get one normalized AroFlo user by canonical user ID.",
            input_schema={"type": "object", "additionalProperties": False,
                "required": ["record_id"], "properties": {"record_id": {"type": "string", "minLength": 1},
                    "transport": {"enum": ["auto", "api"]}}},
            output_schema={"type": "object", "required": ["outcome", "item", "meta"]},
            required_credentials=API_CREDENTIAL_NAMES, timeout_seconds=20,
            data_classification="confidential",
            errors=("invalid_input", "transport_unavailable", "credential_not_configured", "provider_unavailable"),
            agent=AgentCapabilityContract(
                exposed=True, title="Get an AroFlo user",
                summary="Retrieve one normalized AroFlo user from an exact canonical user ID.",
                domains=("aroflo", "users"), use_when=("An exact AroFlo user ID is known.",),
                do_not_use_when=("Only a name or email is available; use user search instead.",),
                risk_category="read", risk_level="low", confirmation="never",
                required_permissions=("aroflo.users.read",),
            ),
        ),
        "users_search_v1": ActionContract(
            capability="aroflo.users.search",
            description="Search normalized AroFlo users with one or more allowlisted filters.",
            input_schema={"type": "object", "additionalProperties": False,
                "required": ["filters"], "properties": {"filters": {"type": "array", "minItems": 1, "maxItems": 20},
                    "sort": {"type": "array"}, "pagination": {"type": "object"},
                    "transport": {"enum": ["auto", "api"]}}},
            output_schema={"type": "object", "required": ["outcome", "items", "pagination", "meta"]},
            required_credentials=API_CREDENTIAL_NAMES, timeout_seconds=30,
            data_classification="confidential",
            errors=("invalid_input", "unsupported_filter", "transport_unavailable",
                    "credential_not_configured", "provider_unavailable"),
            agent=AgentCapabilityContract(
                exposed=True, title="Search AroFlo users",
                summary="Resolve AroFlo users by name, email, username, phone, position, status, or dates.",
                domains=("aroflo", "users", "identity_resolution"),
                use_when=("The user identifies AroFlo staff by name, email, phone, position, or combined criteria.",),
                do_not_use_when=("The request changes user data.",),
                risk_category="read", risk_level="low", confirmation="never",
                required_permissions=("aroflo.users.read",),
            ),
        ),
    },
    wordpress_package=WordPressPackageContract(
        package_id="aroflo_connector_wp",
        version="1.5.0-dev",
        source_directory="wordpress-pack/aroflo-connector-wp",
        entrypoint="aroflo-connector-wp.php",
        plugin_file="aroflo-connector-wp/aroflo-connector-wp.php",
        capabilities=(
            "platform.health",
            "platform.credentials.test",
            "aroflo.api.configure",
            "aroflo.catalog.read",
            "aroflo.bots.list",
            "aroflo.bot.configure",
            "aroflo.bot.status",
            "aroflo.bot.connect",
            "aroflo.bot.mfa.submit",
            "aroflo.bot.disconnect",
            "aroflo.users.list",
            "aroflo.users.get",
            "aroflo.users.search",
        ),
    ),
)


def handle_request(context, action):
    """Dispatch Standard v1 actions while legacy entrypoints remain available."""
    if action == "health_v1":
        return _health(context)
    if action == "test_connection_v1":
        return _test_connection(context)
    if action == "configure_api_v1":
        return _configure_api(context)
    if action == "zones_v1":
        return _zones(context)
    if action == "bots_v1":
        return _bots(context)
    if action == "configure_bot_v1":
        return _configure_bot(context)
    if action in {"bot_status_v1", "connect_bot_v1", "submit_bot_mfa_v1", "disconnect_bot_v1"}:
        return _bot_session_action(context, action)
    if action in {"users_list_v1", "users_get_v1", "users_search_v1"}:
        return _users_read(context, action)
    return error_envelope(
        app_id=APP_MANIFEST.app_id,
        action=action,
        request_id=_request_id(context),
        code="action_not_found",
        message="The requested action is not supported.",
    ), 404


def _health(context):
    configured = False
    vault_ready = True
    tenant_id = _tenant_id(context)
    if tenant_id:
        try:
            configured = _api_credentials_configured(tenant_id)
        except (SecretConfigurationError, SecretIntegrityError):
            vault_ready = False
    return success_envelope(
        app_id=APP_MANIFEST.app_id,
        action="health_v1",
        request_id=_request_id(context),
        data={
            "healthy": True,
            "vault_ready": vault_ready,
            "api_credentials_configured": configured,
            "provider_checked": False,
        },
    )


def _test_connection(context):
    tenant_id = _tenant_id(context)
    if not tenant_id:
        return _error(
            context,
            "test_connection_v1",
            "tenant_context_missing",
            "A trusted tenant context is required.",
            400,
        )
    try:
        settings = AroFloSettings.from_credentials(_load_api_credentials(tenant_id))
    except AroFloConfigError:
        return _error(
            context,
            "test_connection_v1",
            "credential_not_configured",
            "AroFlo API credentials are not configured for this tenant.",
            409,
        )
    except (SecretConfigurationError, SecretIntegrityError):
        return _error(
            context,
            "test_connection_v1",
            "credential_vault_unavailable",
            "The credential vault is temporarily unavailable.",
            503,
            retryable=True,
        )

    try:
        AroFloClient(settings=settings).request(
            "",
            method="GET",
            params=[("zone", "lastupdate"), ("page", "1")],
            timeout=min(settings.timeout, 20),
        )
    except AroFloError:
        return _error(
            context,
            "test_connection_v1",
            "provider_connection_failed",
            "AroFlo API credentials could not be validated.",
            502,
            retryable=True,
        )

    return success_envelope(
        app_id=APP_MANIFEST.app_id,
        action="test_connection_v1",
        request_id=_request_id(context),
        data={
            "connected": True,
            "provider": "AroFlo",
            "transport": "api",
            "tested_at": datetime.now(timezone.utc).isoformat(),
        },
    )


def _configure_api(context):
    tenant_id = _tenant_id(context)
    if not tenant_id:
        return _error(context, "configure_api_v1", "tenant_context_missing",
                      "A trusted tenant context is required.", 400)
    payload = (context or {}).get("payload") or {}
    allowed = API_CREDENTIAL_NAMES + ("aroflo_host_ip",)
    values = {name: str(payload.get(name) or "").strip() for name in allowed}
    missing = [name for name in API_CREDENTIAL_NAMES if not values[name]]
    if missing:
        return _error(context, "configure_api_v1", "invalid_input",
                      "All required AroFlo API credentials must be supplied.", 400)
    if not values["aroflo_base_url"].lower().startswith("https://"):
        return _error(context, "configure_api_v1", "invalid_input",
                      "The AroFlo API URL must use HTTPS.", 400)
    try:
        manager = SecretsManager()
        for name, value in values.items():
            if value:
                manager.put_secret(tenant_id=tenant_id, app_id=APP_MANIFEST.app_id,
                                   secret_name=name, secret_value=value,
                                   actor_id=str((context or {}).get("subject") or "platform"))
    except (ValueError, LookupError, SecretConfigurationError, SecretIntegrityError):
        return _error(context, "configure_api_v1", "credential_vault_unavailable",
                      "The API credentials could not be stored securely.", 503)
    return success_envelope(app_id=APP_MANIFEST.app_id, action="configure_api_v1",
        request_id=_request_id(context), data={
            "configured": True,
            "fields": {name: bool(value) for name, value in values.items()},
        })


def _zones(context):
    manifest = build_capabilities_manifest(object())
    zones = []
    operation_count = 0
    for zone in manifest.get("zones", []):
        operations = [
            {
                "code": operation.get("code", ""),
                "label": operation.get("label", ""),
                "description": operation.get("description", ""),
                "http_method": operation.get("http_method", ""),
                "access": operation.get("side_effect", "read"),
                "idempotent": bool(operation.get("idempotent", True)),
                "requires_confirmation": bool(operation.get("requires_confirmation", False)),
                "published": False,
            }
            for operation in zone.get("operations", [])
        ]
        operation_count += len(operations)
        zones.append(
            {
                "code": zone.get("code", ""),
                "label": zone.get("label", ""),
                "description": zone.get("description", ""),
                "operations": operations,
            }
        )
    return success_envelope(
        app_id=APP_MANIFEST.app_id,
        action="zones_v1",
        request_id=_request_id(context),
        data={
            "zones": zones,
            "zone_count": len(zones),
            "operation_count": operation_count,
            "provider_checked": False,
        },
        meta={"execution_enabled": False},
    )


def _bots(context):
    tenant_id = _tenant_id(context)
    if not tenant_id:
        return _error(context, "bots_v1", "tenant_context_missing", "A trusted tenant context is required.", 400)
    summary = bot_pool_summary(tenant_id)
    return success_envelope(app_id=APP_MANIFEST.app_id, action="bots_v1",
        request_id=_request_id(context), data=summary)


def _configure_bot(context):
    tenant_id = _tenant_id(context)
    if not tenant_id:
        return _error(context, "configure_bot_v1", "tenant_context_missing", "A trusted tenant context is required.", 400)
    payload = (context or {}).get("payload") or {}
    try:
        bot = configure_bot(tenant_id, bot_id=payload.get("bot_id", ""),
            label=payload.get("label", ""), username=payload.get("username", ""),
            password=payload.get("password", ""), zones=payload.get("zones") or [],
            base_url=payload.get("base_url", ""),
            enabled=payload.get("enabled", True),
            remember_30_days=payload.get("remember_30_days", False),
            actor_id=str((context or {}).get("subject") or "platform"))
    except (ValueError, LookupError):
        return _error(context, "configure_bot_v1", "invalid_input", "The BOT profile could not be configured.", 400)
    return success_envelope(app_id=APP_MANIFEST.app_id, action="configure_bot_v1",
        request_id=_request_id(context), data={"bot": bot})


def _bot_session_action(context, action):
    tenant_id, payload = _tenant_id(context), (context or {}).get("payload") or {}
    bot_id = str(payload.get("bot_id") or "").strip().lower()
    if not tenant_id or not bot_id:
        return _error(context, action, "invalid_input", "A valid BOT ID is required.", 400)
    if not any(bot["bot_id"] == bot_id and bot["enabled"] for bot in list_bots(tenant_id)):
        return _error(context, action, "bot_not_found", "The BOT profile is unavailable.", 404)
    mapping = {
        "bot_status_v1": ("GET", "/ui/session/status", {}, 15),
        "connect_bot_v1": ("POST", "/ui/session/connect", {}, 90),
        "disconnect_bot_v1": ("POST", "/ui/session/close", {}, 20),
    }
    if action == "submit_bot_mfa_v1":
        code = str(payload.get("code") or "").strip()
        if len(code) != 6 or not code.isdigit() or not payload.get("idempotency_key"):
            return _error(context, action, "invalid_input", "A six-digit MFA code and idempotency key are required.", 400)
        bot = next(bot for bot in list_bots(tenant_id) if bot["bot_id"] == bot_id)
        method, path, worker_payload, timeout = "POST", "/ui/session/mfa", {
            "code": code, "remember_30_days": bool(bot.get("remember_30_days")),
        }, 90
    else:
        method, path, worker_payload, timeout = mapping[action]
    try:
        result = AroFloWorkerClient().call(method, path, tenant_id=tenant_id, bot_id=bot_id,
                                          payload=worker_payload, timeout=timeout)
    except WorkerError as exc:
        status = 409 if exc.code == "bot_busy" else 503
        return _error(context, action, exc.code, "The BOT worker could not complete the request.",
                      status, retryable=exc.retryable)
    state = str(result.get("state") or "unknown").lower()
    return success_envelope(app_id=APP_MANIFEST.app_id, action=action,
        request_id=_request_id(context), data={"bot_id": bot_id, "state": state})


def _users_read(context, action):
    tenant_id = _tenant_id(context)
    if not tenant_id:
        return _error(context, action, "tenant_context_missing", "A trusted tenant context is required.", 400)
    payload = dict((context or {}).get("payload") or {})
    payload["operation"] = {
        "users_list_v1": "list", "users_get_v1": "get", "users_search_v1": "search",
    }[action]
    try:
        settings = AroFloSettings.from_credentials(_load_api_credentials(tenant_id))
        data = query_users(AroFloClient(settings=settings), payload)
    except AroFloConfigError:
        return _error(context, action, "credential_not_configured",
                      "AroFlo API credentials are not configured for this tenant.", 409)
    except UsersContractError as exc:
        return _error(context, action, exc.code, str(exc), 400)
    except AroFloError:
        return _error(context, action, "provider_unavailable",
                      "AroFlo could not complete the users query.", 502, retryable=True)
    return success_envelope(app_id=APP_MANIFEST.app_id, action=action,
        request_id=_request_id(context), data=data)


def _load_api_credentials(tenant_id: str) -> dict[str, str]:
    manager = SecretsManager()
    names = API_CREDENTIAL_NAMES + ("aroflo_host_ip",)
    return {
        name: value
        for name in names
        if (value := manager.get_secret(
            tenant_id=tenant_id,
            app_id=APP_MANIFEST.app_id,
            secret_name=name,
        )) is not None
    }


def _api_credentials_configured(tenant_id: str) -> bool:
    credentials = _load_api_credentials(tenant_id)
    return all(bool(credentials.get(name)) for name in API_CREDENTIAL_NAMES)


def _tenant_id(context) -> str:
    return str((((context or {}).get("tenant") or {}).get("tenant_id")) or "")


def _request_id(context) -> str:
    return str((context or {}).get("request_id") or "")


def _error(context, action, code, message, status, *, retryable=False):
    return error_envelope(
        app_id=APP_MANIFEST.app_id,
        action=action,
        request_id=_request_id(context),
        code=code,
        message=message,
        retryable=retryable,
    ), status


def init_app(app):
    """Register the legacy Flask blueprint during the migration window."""
    from .api.v1.blueprint import bp as aroflo_v1_bp

    app.register_blueprint(aroflo_v1_bp, url_prefix="/api/aroflo/v1")
