"""SCIM Directory Sync — WorkOS webhook handler.

Handles user provisioning/deprovisioning and group-based role
assignment via WorkOS SCIM directory sync webhooks.
"""

from __future__ import annotations

import hashlib
import hmac
import logging
from datetime import UTC, datetime
from typing import Any

from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse

_log = logging.getLogger(__name__)

scim_router = APIRouter(prefix="/api/v1/scim", tags=["scim"])

_VALID_EVENTS = {
    "dsync.user.created",
    "dsync.user.updated",
    "dsync.user.deleted",
    "dsync.group.user_added",
    "dsync.group.user_removed",
}


def _get_webhook_secret() -> str:
    """Load WorkOS webhook secret from Secret Manager."""
    from google.cloud import secretmanager

    client = secretmanager.SecretManagerServiceClient()
    name = "projects/complyform-prod/secrets/workos-webhook-secret/versions/latest"
    response = client.access_secret_version(request={"name": name})
    return response.payload.data.decode()


def _verify_webhook_signature(
    payload: bytes,
    signature: str,
    secret: str,
) -> bool:
    """Verify WorkOS webhook HMAC-SHA256 signature."""
    expected = hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, signature)


async def _find_customer_by_connection(
    connection_id: str,
) -> tuple[str, dict[str, Any]] | None:
    """Find customer with matching SSO connection ID."""
    from backend.shared.firestore import get_db

    query = (
        get_db()
        .collection("sso_configs")
        .where("broker_connection_id", "==", connection_id)
        .limit(1)
    )
    docs = [doc async for doc in query.stream()]
    if not docs:
        return None

    data: dict[str, Any] = docs[0].to_dict()  # type: ignore[assignment]
    return data["customer_id"], data


async def _get_member_count(customer_id: str) -> int:
    """Count active members for a customer."""
    from backend.shared.firestore import get_db

    query = (
        get_db()
        .collection("customers")
        .document(customer_id)
        .collection("members")
        .where("status", "==", "active")
    )
    count = 0
    async for _ in query.stream():
        count += 1
    return count


async def _get_member_by_email(
    customer_id: str,
    email: str,
) -> tuple[str, dict[str, Any]] | None:
    """Find member doc by email."""
    from backend.shared.firestore import get_db

    email_hash = hashlib.sha256(email.lower().encode()).hexdigest()
    doc = (
        await get_db()
        .collection("customers")
        .document(customer_id)
        .collection("members")
        .document(email_hash)
        .get()
    )
    if not doc.exists:
        return None
    return email_hash, doc.to_dict()  # type: ignore[return-value]


async def _revoke_sessions_for_email(email: str) -> None:
    """Delete all sessions for a given email."""
    from backend.shared.firestore import get_db

    query = get_db().collection("sessions").where("email", "==", email)
    async for doc in query.stream():
        await doc.reference.delete()


async def _write_scim_audit(
    customer_id: str,
    event: str,
    metadata: dict[str, str | int | bool | None],
) -> None:
    """Write SCIM audit event (non-fatal)."""
    try:
        from backend.dashboard.audit import write_audit_event

        await write_audit_event(
            customer_id=customer_id,
            event=event,
            actor_email="scim-directory-sync",
            actor_role="system",
            ip_hash="scim-webhook",
            metadata=metadata,
        )
    except Exception:
        _log.exception("Failed to write SCIM audit event %s", event)


@scim_router.post("/webhook")
async def scim_webhook(request: Request) -> JSONResponse:
    """Handle WorkOS SCIM Directory Sync webhooks.

    Verifies signature, then dispatches to user/group handlers.
    """
    from src.complyform.core.errors import SCIMWebhookValidationError

    payload = await request.body()
    signature = request.headers.get("workos-signature", "")

    try:
        secret = _get_webhook_secret()
    except Exception:
        _log.exception("Failed to load webhook secret")
        return JSONResponse(
            {"error": "Internal configuration error"},
            status_code=500,
        )

    if not _verify_webhook_signature(payload, signature, secret):
        raise SCIMWebhookValidationError("HMAC mismatch")

    body: dict[str, Any] = await request.json()
    event_type = body.get("event", "")
    data = body.get("data", {})

    if event_type not in _VALID_EVENTS:
        return JSONResponse({"status": "ignored"})

    # Resolve customer from connection
    directory_id = data.get("directory_id", "")
    connection_id = body.get("connection_id", directory_id)

    customer_info = await _find_customer_by_connection(connection_id)
    if customer_info is None:
        _log.warning("No customer for connection %s", connection_id)
        return JSONResponse({"status": "no_customer"})

    customer_id, sso_data = customer_info
    group_mapping: dict[str, str] = sso_data.get("group_mapping", {})

    if event_type == "dsync.user.created":
        return await _handle_user_created(customer_id, data, group_mapping)
    if event_type == "dsync.user.updated":
        return await _handle_user_updated(customer_id, data)
    if event_type == "dsync.user.deleted":
        return await _handle_user_deleted(customer_id, data)
    if event_type == "dsync.group.user_added":
        return await _handle_group_user_added(customer_id, data, group_mapping)
    if event_type == "dsync.group.user_removed":
        return await _handle_group_user_removed(customer_id, data, group_mapping)

    return JSONResponse({"status": "ok"})


async def _handle_user_created(
    customer_id: str,
    data: dict[str, Any],
    group_mapping: dict[str, str],
) -> JSONResponse:
    """Create member doc for SCIM-provisioned user."""
    from backend.shared.firestore import get_db
    from backend.shared.tier_config import TIER_CONFIG

    email = data.get("emails", [{}])[0].get("value", "")
    if not email:
        return JSONResponse({"status": "no_email"}, status_code=400)

    # Check member limit
    cust_doc = await get_db().collection("customers").document(customer_id).get()
    if cust_doc.exists:
        cust_data: dict[str, Any] = cust_doc.to_dict()  # type: ignore[assignment]
        tier = cust_data.get("tier", "community")
        tier_cfg = TIER_CONFIG.get(tier, {})
        member_limit = tier_cfg.get("member_limit")
        if member_limit is not None:
            current_count = await _get_member_count(customer_id)
            if current_count >= int(member_limit):
                return JSONResponse(
                    {"error": "Member limit reached"},
                    status_code=409,
                )

    # Determine role from groups
    groups = data.get("groups", [])
    role = "viewer"
    for group in groups:
        group_name = group.get("name", "")
        if group_name in group_mapping:
            role = group_mapping[group_name]
            break

    email_hash = hashlib.sha256(email.lower().encode()).hexdigest()
    now = datetime.now(UTC).isoformat()

    member_doc = {
        "email_hash": email_hash,
        "email": email,
        "role": role,
        "status": "active",
        "invited_by": "scim-directory-sync",
        "invited_at": now,
        "accepted_at": now,
        "last_accessed_at": None,
        "provisioned_via": "scim",
    }

    await (
        get_db()
        .collection("customers")
        .document(customer_id)
        .collection("members")
        .document(email_hash)
        .set(member_doc)
    )

    await _write_scim_audit(
        customer_id,
        "scim.user_provisioned",
        {"email": email, "role": role},
    )

    return JSONResponse({"status": "created"}, status_code=201)


async def _handle_user_updated(
    customer_id: str,
    data: dict[str, Any],
) -> JSONResponse:
    """Update member email or attributes."""
    from backend.shared.firestore import get_db

    email = data.get("emails", [{}])[0].get("value", "")
    if not email:
        return JSONResponse({"status": "no_email"}, status_code=400)

    member = await _get_member_by_email(customer_id, email)
    if member is None:
        return JSONResponse({"status": "not_found"}, status_code=404)

    email_hash, _ = member
    await (
        get_db()
        .collection("customers")
        .document(customer_id)
        .collection("members")
        .document(email_hash)
        .update({"email": email})
    )

    return JSONResponse({"status": "updated"})


async def _handle_user_deleted(
    customer_id: str,
    data: dict[str, Any],
) -> JSONResponse:
    """Deprovision user — set status, revoke sessions."""
    from backend.shared.firestore import get_db

    email = data.get("emails", [{}])[0].get("value", "")
    if not email:
        return JSONResponse({"status": "no_email"}, status_code=400)

    member = await _get_member_by_email(customer_id, email)
    if member is None:
        return JSONResponse({"status": "not_found"}, status_code=404)

    email_hash, member_data = member

    # Owner cannot be deprovisioned via SCIM
    if member_data.get("role") == "owner":
        _log.warning(
            "SCIM attempted to deprovision owner %s — skipping",
            email,
        )
        return JSONResponse({"status": "owner_skipped"})

    await (
        get_db()
        .collection("customers")
        .document(customer_id)
        .collection("members")
        .document(email_hash)
        .update({"status": "deprovisioned"})
    )

    await _revoke_sessions_for_email(email)

    await _write_scim_audit(
        customer_id,
        "scim.user_deprovisioned",
        {"email": email},
    )

    return JSONResponse({"status": "deprovisioned"})


async def _handle_group_user_added(
    customer_id: str,
    data: dict[str, Any],
    group_mapping: dict[str, str],
) -> JSONResponse:
    """Update role when user added to mapped group."""
    from backend.shared.firestore import get_db

    user_data = data.get("user", {})
    email = user_data.get("emails", [{}])[0].get("value", "")
    group_name = data.get("group", {}).get("name", "")

    if not email or not group_name:
        return JSONResponse({"status": "missing_data"}, status_code=400)

    new_role = group_mapping.get(group_name)
    if new_role is None:
        return JSONResponse({"status": "unmapped_group"})

    member = await _get_member_by_email(customer_id, email)
    if member is None:
        return JSONResponse({"status": "not_found"}, status_code=404)

    email_hash, member_data = member
    old_role = member_data.get("role", "viewer")

    if old_role == new_role:
        return JSONResponse({"status": "no_change"})

    await (
        get_db()
        .collection("customers")
        .document(customer_id)
        .collection("members")
        .document(email_hash)
        .update({"role": new_role})
    )

    await _write_scim_audit(
        customer_id,
        "scim.user_role_changed",
        {"email": email, "old_role": old_role, "new_role": new_role},
    )

    return JSONResponse({"status": "role_updated"})


async def _handle_group_user_removed(
    customer_id: str,
    data: dict[str, Any],
    group_mapping: dict[str, str],
) -> JSONResponse:
    """Revert role when user removed from mapped group."""
    from backend.shared.firestore import get_db

    user_data = data.get("user", {})
    email = user_data.get("emails", [{}])[0].get("value", "")
    group_name = data.get("group", {}).get("name", "")

    if not email or not group_name:
        return JSONResponse({"status": "missing_data"}, status_code=400)

    if group_name not in group_mapping:
        return JSONResponse({"status": "unmapped_group"})

    member = await _get_member_by_email(customer_id, email)
    if member is None:
        return JSONResponse({"status": "not_found"}, status_code=404)

    email_hash, member_data = member
    old_role = member_data.get("role", "viewer")

    await (
        get_db()
        .collection("customers")
        .document(customer_id)
        .collection("members")
        .document(email_hash)
        .update({"role": "viewer"})
    )

    if old_role != "viewer":
        await _write_scim_audit(
            customer_id,
            "scim.user_role_changed",
            {"email": email, "old_role": old_role, "new_role": "viewer"},
        )

    return JSONResponse({"status": "role_reverted"})
