"""
Inbound webhook API endpoints for data ingestion and integrations.
"""

import base64
import hashlib
import hmac
import json
import logging
import os
from typing import Optional, Dict, Any, List
from datetime import datetime, timedelta
from uuid import UUID

from fastapi import (
    APIRouter,
    Request,
    Response,
    Depends,
    HTTPException,
    Header,
    Query,
    Body,
)
from fastapi.responses import JSONResponse
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from sqlalchemy.exc import IntegrityError
import redis.asyncio as redis

from fmatch.saas.database import get_async_session, get_redis_client
from fmatch.saas.auth_security import require_account
from fmatch.saas.models import BYOEnrichmentJob
from fmatch.saas.model_defs.webhook_models import (
    InboundWebhook,
    WebhookApiKey,
    WebhookEndpoint,
    WebhookDelivery,
    WebhookEventLog,
    WebhookStatus,
)
from fmatch.saas.services.webhook_service import WebhookService
from fmatch.saas.services.secret_storage import get_saas_secret_backend
from fmatch.saas.services.gmail_support import GmailSupportService, ensure_mailbox_integration
from fmatch.saas import settings

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/v2/webhooks", tags=["webhooks"])


async def verify_webhook_signature(
    request: Request,
    signature: Optional[str] = Header(None, alias="X-Webhook-Signature"),
    signature_256: Optional[str] = Header(None, alias="X-Webhook-Signature-256"),
    slack_signature: Optional[str] = Header(None, alias="X-Slack-Signature"),
    slack_timestamp: Optional[str] = Header(None, alias="X-Slack-Request-Timestamp"),
) -> bool:
    """Verify webhook signature based on the source."""

    # Get raw body
    body = await request.body()

    # Slack signature verification
    if slack_signature and slack_timestamp:
        return verify_slack_signature(body, slack_timestamp, slack_signature)

    # Generic HMAC verification
    if signature or signature_256:
        # Try to identify the endpoint based on path or other headers
        # This would need to look up the endpoint's secret
        # For now, return True if signature is present (implement full verification)
        return bool(signature or signature_256)

    # No signature provided
    return False


def verify_slack_signature(body: bytes, timestamp: str, signature: str) -> bool:
    """Verify Slack webhook signature."""
    if not settings.SLACK_SIGNING_SECRET:
        logger.warning("Slack signing secret not configured")
        return False

    # Check timestamp to prevent replay attacks (must be within 5 minutes)
    try:
        ts = int(timestamp)
        if abs(datetime.utcnow().timestamp() - ts) > 300:
            logger.warning("Slack timestamp too old")
            return False
    except (ValueError, TypeError):
        return False

    # Compute expected signature
    sig_basestring = f"v0:{timestamp}:{body.decode('utf-8')}"
    expected_sig = (
        "v0="
        + hmac.new(
            settings.SLACK_SIGNING_SECRET.encode(),
            sig_basestring.encode(),
            hashlib.sha256,
        ).hexdigest()
    )

    # Compare signatures
    return hmac.compare_digest(expected_sig, signature)


async def get_api_key(
    api_key: Optional[str] = Header(None, alias="X-API-Key"),
    authorization: Optional[str] = Header(None),
) -> Optional[str]:
    """Extract API key from headers."""
    if api_key:
        return api_key

    if authorization and authorization.startswith("Bearer "):
        return authorization[7:]

    return None


async def verify_api_key(
    api_key: str, session: AsyncSession, source_ip: str
) -> Optional[WebhookApiKey]:
    """Verify API key and check permissions."""
    # Hash the provided key
    key_hash = hashlib.sha256(api_key.encode()).hexdigest()

    # Look up the key
    query = select(WebhookApiKey).where(
        WebhookApiKey.key_hash == key_hash, WebhookApiKey.active == True
    )
    result = await session.execute(query)
    key_record = result.scalar_one_or_none()

    if not key_record:
        return None

    # Check expiration
    if key_record.expires_at and key_record.expires_at < datetime.utcnow():
        return None

    # Check IP whitelist if configured
    if key_record.ip_whitelist and source_ip not in key_record.ip_whitelist:
        logger.warning(f"API key {key_record.id} used from unauthorized IP {source_ip}")
        return None

    # Update usage stats
    key_record.last_used_at = datetime.utcnow()
    key_record.last_used_ip = source_ip
    key_record.total_requests += 1

    await session.commit()

    return key_record


@router.post("/ingest")
async def ingest_webhook(
    request: Request,
    session: AsyncSession = Depends(get_async_session),
    redis_client: redis.Redis = Depends(get_redis_client),
    api_key: Optional[str] = Depends(get_api_key),
    content_type: Optional[str] = Header(None),
    x_correlation_id: Optional[str] = Header(None),
    x_integration_id: Optional[str] = Header(None),
):
    """
    Generic webhook ingestion endpoint for receiving data from external systems.

    Supports JSON, CSV, and NDJSON formats.
    Requires API key authentication.
    """
    # Get source IP
    source_ip = request.client.host if request.client else None

    # Verify API key
    if not api_key:
        raise HTTPException(status_code=401, detail="API key required")

    key_record = await verify_api_key(api_key, session, source_ip)
    if not key_record:
        raise HTTPException(status_code=401, detail="Invalid API key")

    # Check rate limiting
    rate_limit_key = f"webhook:rate_limit:{key_record.id}"
    current_count = await redis_client.incr(rate_limit_key)

    if current_count == 1:
        # Set expiration on first request
        await redis_client.expire(rate_limit_key, 60)

    if current_count > key_record.rate_limit_per_minute:
        key_record.failed_requests += 1
        await session.commit()
        raise HTTPException(status_code=429, detail="Rate limit exceeded")

    # Get request body
    body = await request.body()

    # Parse content based on content type
    try:
        if content_type and "json" in content_type.lower():
            payload = json.loads(body)
            data_format = "json"
        elif content_type and "csv" in content_type.lower():
            payload = {"raw": body.decode("utf-8")}
            data_format = "csv"
        elif content_type and "ndjson" in content_type.lower():
            payload = {"raw": body.decode("utf-8")}
            data_format = "ndjson"
        else:
            # Try to parse as JSON by default
            try:
                payload = json.loads(body)
                data_format = "json"
            except:
                payload = {"raw": body.decode("utf-8")}
                data_format = "unknown"
    except Exception as e:
        logger.error(f"Failed to parse webhook payload: {e}")
        raise HTTPException(status_code=400, detail="Invalid payload format")

    # Create inbound webhook record
    inbound = InboundWebhook(
        account_id=key_record.account_id,
        source="api",
        source_ip=source_ip,
        user_agent=request.headers.get("User-Agent"),
        method=request.method,
        path=str(request.url.path),
        headers=dict(request.headers),
        query_params=dict(request.query_params),
        payload=payload,
        raw_body=body.decode("utf-8")[:10000],  # Limit size
        content_type=content_type,
        payload_size=len(body),
        api_key_used=key_record.key_prefix,
        status="received",
        data_format=data_format,
        correlation_id=x_correlation_id,
        integration_id=x_integration_id,
    )

    session.add(inbound)
    await session.flush()

    # Queue for async processing
    await redis_client.lpush(
        "webhook:inbound:queue",
        json.dumps(
            {
                "id": str(inbound.id),
                "account_id": key_record.account_id,
                "format": data_format,
                "integration_id": x_integration_id,
            }
        ),
    )

    # Update success stats
    key_record.successful_requests += 1

    # Log event
    event_log = WebhookEventLog(
        account_id=key_record.account_id,
        event_type="webhook.received",
        event_category="inbound",
        inbound_id=inbound.id,
        api_key_id=key_record.id,
        description=f"Received {data_format} webhook via API",
        event_metadata={
            "size": len(body),
            "format": data_format,
            "correlation_id": x_correlation_id,
        },
        actor_type="api",
        actor_id=key_record.name,
        actor_ip=source_ip,
    )
    session.add(event_log)

    await session.commit()

    return JSONResponse(
        status_code=202,
        content={
            "status": "accepted",
            "id": str(inbound.id),
            "correlation_id": x_correlation_id or str(inbound.id),
            "message": "Webhook received and queued for processing",
        },
    )


@router.post("/slack/interactive")
async def slack_interactive_webhook(
    request: Request,
    session: AsyncSession = Depends(get_async_session),
    redis_client: redis.Redis = Depends(get_redis_client),
):
    """
    Handle Slack interactive components (buttons, select menus, etc).
    """
    # Verify Slack signature
    body = await request.body()
    timestamp = request.headers.get("X-Slack-Request-Timestamp")
    signature = request.headers.get("X-Slack-Signature")

    if not verify_slack_signature(body, timestamp, signature):
        raise HTTPException(status_code=401, detail="Invalid signature")

    # Parse payload (Slack sends as form data with payload field)
    try:
        form_data = await request.form()
        payload = json.loads(form_data.get("payload", "{}"))
    except Exception as e:
        logger.error(f"Failed to parse Slack payload: {e}")
        raise HTTPException(status_code=400, detail="Invalid payload")

    # Extract key information
    team_id = payload.get("team", {}).get("id")
    user_id = payload.get("user", {}).get("id")
    action_type = payload.get("type")  # block_actions, view_submission, etc.

    # Create inbound webhook record
    inbound = InboundWebhook(
        source="slack",
        source_ip=request.client.host if request.client else None,
        user_agent=request.headers.get("User-Agent"),
        method=request.method,
        path=str(request.url.path),
        headers=dict(request.headers),
        payload=payload,
        raw_body=body.decode("utf-8")[:10000],
        signature=signature,
        signature_valid=True,
        status="received",
        data_format="slack_interactive",
        integration_id=team_id,
    )

    session.add(inbound)
    await session.flush()

    # Queue for async processing based on action type
    queue_name = f"webhook:slack:{action_type}"
    await redis_client.lpush(
        queue_name,
        json.dumps(
            {
                "id": str(inbound.id),
                "team_id": team_id,
                "user_id": user_id,
                "action_type": action_type,
                "payload": payload,
            }
        ),
    )

    await session.commit()

    # Slack expects immediate response for interactive components
    # Return appropriate response based on action type
    if action_type == "block_actions":
        # Acknowledge the action
        return Response(status_code=200)
    elif action_type == "view_submission":
        # Close the modal by default
        return JSONResponse(content={"response_action": "clear"})
    else:
        return Response(status_code=200)


@router.post("/slack/events")
async def slack_events_webhook(
    request: Request,
    session: AsyncSession = Depends(get_async_session),
    redis_client: redis.Redis = Depends(get_redis_client),
):
    """
    Handle Slack Events API webhooks.
    """
    # Verify Slack signature
    body = await request.body()
    timestamp = request.headers.get("X-Slack-Request-Timestamp")
    signature = request.headers.get("X-Slack-Signature")

    if not verify_slack_signature(body, timestamp, signature):
        raise HTTPException(status_code=401, detail="Invalid signature")

    # Parse payload
    try:
        payload = json.loads(body)
    except Exception as e:
        logger.error(f"Failed to parse Slack payload: {e}")
        raise HTTPException(status_code=400, detail="Invalid payload")

    # Handle URL verification challenge
    if payload.get("type") == "url_verification":
        return JSONResponse(content={"challenge": payload.get("challenge")})

    # Extract event information
    event = payload.get("event", {})
    event_type = event.get("type")
    team_id = payload.get("team_id")

    # Create inbound webhook record
    inbound = InboundWebhook(
        source="slack_events",
        source_ip=request.client.host if request.client else None,
        user_agent=request.headers.get("User-Agent"),
        method=request.method,
        path=str(request.url.path),
        headers=dict(request.headers),
        payload=payload,
        raw_body=body.decode("utf-8")[:10000],
        signature=signature,
        signature_valid=True,
        status="received",
        data_format="slack_event",
        integration_id=team_id,
    )

    session.add(inbound)
    await session.flush()

    # Queue for async processing
    await redis_client.lpush(
        "webhook:slack:events",
        json.dumps(
            {
                "id": str(inbound.id),
                "team_id": team_id,
                "event_type": event_type,
                "event": event,
            }
        ),
    )

    await session.commit()

    # Acknowledge receipt
    return Response(status_code=200)


# NOTE: The generic dynamic webhook endpoint is intentionally defined AFTER
# all management routes (endpoints, deliveries, api-keys) to avoid collisions
# like POST /api-keys being captured by /{integration_id}.


# Management endpoints for webhook configuration
@router.get("/endpoints")
async def list_webhook_endpoints(
    account_id: str = Depends(require_account),
    session: AsyncSession = Depends(get_async_session),
    active_only: bool = Query(True, description="Only return active endpoints"),
):
    """List webhook endpoints for the current account."""
    query = select(WebhookEndpoint).where(WebhookEndpoint.account_id == account_id)

    if active_only:
        query = query.where(WebhookEndpoint.active == True)

    result = await session.execute(query)
    endpoints = result.scalars().all()

    return {
        "endpoints": [
            {
                "id": str(endpoint.id),
                "name": endpoint.name,
                "url": endpoint.url,
                "events": endpoint.events,
                "active": endpoint.active,
                "verified": endpoint.verified,
                "consecutive_failures": endpoint.consecutive_failures,
                "last_success_at": endpoint.last_success_at,
                "last_failure_at": endpoint.last_failure_at,
                "total_deliveries": endpoint.total_deliveries,
                "successful_deliveries": endpoint.successful_deliveries,
                "created_at": endpoint.created_at,
            }
            for endpoint in endpoints
        ]
    }


@router.post("/endpoints")
async def create_webhook_endpoint(
    account_id: str = Depends(require_account),
    session: AsyncSession = Depends(get_async_session),
    name: str = Body(...),
    url: str = Body(...),
    events: List[str] = Body(...),
    secret: Optional[str] = Body(None),
    headers: Optional[Dict[str, str]] = Body(None),
    retry_enabled: bool = Body(True),
    max_retries: int = Body(5),
    batch_enabled: bool = Body(False),
    batch_size: int = Body(100),
):
    """Create a new webhook endpoint."""
    # Validate URL
    if not url.startswith(("http://", "https://")):
        raise HTTPException(status_code=400, detail="Invalid URL")

    # Create endpoint
    endpoint = WebhookEndpoint(
        account_id=account_id,
        name=name,
        url=url,
        events=events,
        secret=secret,
        headers=headers or {},
        retry_enabled=retry_enabled,
        max_retries=max_retries,
        batch_enabled=batch_enabled,
        batch_size=batch_size,
        created_by=account_id,
    )

    session.add(endpoint)
    await session.commit()

    return {
        "id": str(endpoint.id),
        "name": endpoint.name,
        "url": endpoint.url,
        "events": endpoint.events,
        "message": "Webhook endpoint created successfully",
    }


@router.post("/endpoints/{endpoint_id}/test")
async def test_webhook_endpoint(
    endpoint_id: str,
    account_id: str = Depends(require_account),
    session: AsyncSession = Depends(get_async_session),
):
    """Send a test webhook to verify endpoint configuration."""
    # Verify endpoint belongs to account
    query = select(WebhookEndpoint).where(
        WebhookEndpoint.id == endpoint_id, WebhookEndpoint.account_id == account_id
    )
    result = await session.execute(query)
    endpoint = result.scalar_one_or_none()

    if not endpoint:
        raise HTTPException(status_code=404, detail="Endpoint not found")

    # Send test webhook
    async with WebhookService() as service:
        success, message = await service.verify_endpoint(endpoint_id, session)

    if success:
        return {"status": "success", "message": message}
    else:
        raise HTTPException(status_code=400, detail=message)


@router.get("/deliveries")
async def list_webhook_deliveries(
    account_id: str = Depends(require_account),
    session: AsyncSession = Depends(get_async_session),
    endpoint_id: Optional[str] = Query(None),
    status: Optional[WebhookStatus] = Query(None),
    limit: int = Query(100, le=1000),
):
    """View webhook delivery history."""
    # Build query
    query = (
        select(WebhookDelivery)
        .join(WebhookEndpoint)
        .where(WebhookEndpoint.account_id == account_id)
        .order_by(WebhookDelivery.created_at.desc())
        .limit(limit)
    )

    if endpoint_id:
        query = query.where(WebhookDelivery.endpoint_id == endpoint_id)

    if status:
        status_val = status.value if isinstance(status, WebhookStatus) else status
        query = query.where(WebhookDelivery.status == status_val)

    result = await session.execute(query)
    deliveries = result.scalars().all()

    return {
        "deliveries": [
            {
                "id": str(delivery.id),
                "endpoint_id": str(delivery.endpoint_id),
                "event_type": delivery.event_type,
                "status": delivery.status,
                "attempts": delivery.attempts,
                "response_status_code": delivery.response_status_code,
                "last_error": delivery.last_error,
                "created_at": delivery.created_at,
                "completed_at": delivery.completed_at,
                "latency_ms": delivery.latency_ms,
            }
            for delivery in deliveries
        ]
    }


@router.post("/deliveries/{delivery_id}/replay")
async def replay_webhook_delivery(
    delivery_id: str,
    account_id: str = Depends(require_account),
    session: AsyncSession = Depends(get_async_session),
    redis_client: redis.Redis = Depends(get_redis_client),
):
    """Replay a failed webhook delivery."""
    # Verify delivery belongs to account
    query = (
        select(WebhookDelivery)
        .join(WebhookEndpoint)
        .where(
            WebhookDelivery.id == delivery_id, WebhookEndpoint.account_id == account_id
        )
    )
    result = await session.execute(query)
    delivery = result.scalar_one_or_none()

    if not delivery:
        raise HTTPException(status_code=404, detail="Delivery not found")

    # Reset delivery for replay
    delivery.status = WebhookStatus.PENDING.value
    delivery.attempts = 0
    delivery.next_retry_at = None
    delivery.last_error = None

    await session.commit()

    # Queue for immediate processing
    await redis_client.zadd(
        "webhook:deliveries:pending", {delivery_id: datetime.utcnow().timestamp()}
    )

    return {
        "status": "success",
        "message": "Webhook delivery queued for replay",
        "delivery_id": delivery_id,
    }


@router.put("/endpoints/{endpoint_id}")
async def update_webhook_endpoint(
    endpoint_id: str,
    account_id: str = Depends(require_account),
    session: AsyncSession = Depends(get_async_session),
    updates: Dict[str, Any] = Body(...),
):
    """Update an existing webhook endpoint (name, url, events, etc.)."""
    result = await session.execute(
        select(WebhookEndpoint).where(
            WebhookEndpoint.id == endpoint_id,
            WebhookEndpoint.account_id == account_id,
        )
    )
    endpoint = result.scalar_one_or_none()
    if not endpoint:
        raise HTTPException(status_code=404, detail="Endpoint not found")

    # Allowed fields to update
    allowed_fields = {
        "name",
        "url",
        "description",
        "events",
        "secret",
        "headers",
        "retry_enabled",
        "max_retries",
        "retry_delay_seconds",
        "retry_backoff_multiplier",
        "timeout_seconds",
        "batch_enabled",
        "batch_size",
        "active",
    }
    for k, v in (updates or {}).items():
        if k in allowed_fields:
            setattr(endpoint, k, v)

    endpoint.updated_at = datetime.utcnow()
    await session.commit()

    return {
        "id": str(endpoint.id),
        "name": endpoint.name,
        "url": endpoint.url,
        "events": endpoint.events,
        "active": endpoint.active,
        "updated_at": endpoint.updated_at,
    }


@router.delete("/endpoints/{endpoint_id}")
async def delete_webhook_endpoint(
    endpoint_id: str,
    account_id: str = Depends(require_account),
    session: AsyncSession = Depends(get_async_session),
):
    """Delete a webhook endpoint by ID."""
    result = await session.execute(
        select(WebhookEndpoint).where(
            WebhookEndpoint.id == endpoint_id,
            WebhookEndpoint.account_id == account_id,
        )
    )
    endpoint = result.scalar_one_or_none()
    if not endpoint:
        raise HTTPException(status_code=404, detail="Endpoint not found")

    await session.delete(endpoint)
    await session.commit()

    return {"status": "deleted", "id": endpoint_id}


# API Key management endpoints
@router.get("/api-keys")
async def list_webhook_api_keys(
    account_id: str = Depends(require_account),
    session: AsyncSession = Depends(get_async_session),
):
    """List webhook API keys for the current account (metadata only)."""
    result = await session.execute(
        select(WebhookApiKey).where(WebhookApiKey.account_id == account_id)
    )
    keys = result.scalars().all()

    return {
        "keys": [
            {
                "id": str(k.id),
                "name": k.name,
                "key_prefix": k.key_prefix,
                "description": k.description,
                "active": k.active,
                "rate_limit_per_minute": k.rate_limit_per_minute,
                "expires_at": k.expires_at,
                "last_used_at": k.last_used_at,
                "last_used_ip": k.last_used_ip,
                "total_requests": k.total_requests,
                "successful_requests": k.successful_requests,
                "failed_requests": k.failed_requests,
                "created_at": k.created_at,
                "scopes": k.scopes or [],
            }
            for k in keys
        ]
    }


@router.post("/api-keys")
async def generate_webhook_api_key(
    account_id: str = Depends(require_account),
    session: AsyncSession = Depends(get_async_session),
    name: str = Body(...),
    description: Optional[str] = Body(None),
    rate_limit_per_minute: int = Body(100),
    expires_in_days: Optional[int] = Body(None),
):
    """Generate and return a new webhook API key for the account."""
    import secrets

    # Generate opaque API key and hash
    raw_key = secrets.token_urlsafe(32)
    key_hash = hashlib.sha256(raw_key.encode()).hexdigest()
    key_prefix = raw_key[:10]

    # Compute expiration
    expires_at = None
    if expires_in_days and expires_in_days > 0:
        expires_at = datetime.utcnow() + timedelta(days=expires_in_days)

    # Create key record
    key_record = WebhookApiKey(
        account_id=account_id,
        name=name,
        description=description,
        key_hash=key_hash,
        key_prefix=key_prefix,
        rate_limit_per_minute=rate_limit_per_minute,
        active=True,
        expires_at=expires_at,
        created_by=account_id,
    )
    session.add(key_record)
    try:
        await session.commit()
    except IntegrityError as e:
        # Likely duplicate key name for this account due to UniqueConstraint
        await session.rollback()
        raise HTTPException(
            status_code=409, detail="An API key with this name already exists"
        ) from e

    return {
        "id": str(key_record.id),
        "key": raw_key,  # return only once
        "key_prefix": key_prefix,
        "name": key_record.name,
        "description": key_record.description,
        "rate_limit_per_minute": key_record.rate_limit_per_minute,
        "expires_at": key_record.expires_at,
        "active": key_record.active,
    }


@router.post("/api-keys/{key_id}/revoke")
async def revoke_webhook_api_key(
    key_id: str,
    account_id: str = Depends(require_account),
    session: AsyncSession = Depends(get_async_session),
):
    """Revoke a webhook API key by ID."""
    result = await session.execute(
        select(WebhookApiKey).where(
            WebhookApiKey.id == key_id,
            WebhookApiKey.account_id == account_id,
        )
    )
    key = result.scalar_one_or_none()
    if not key:
        raise HTTPException(status_code=404, detail="API key not found")

    if key.active:
        key.active = False
        key.revoked_at = datetime.utcnow()
        key.revoked_by = account_id
        await session.commit()

    return {"status": "revoked", "id": str(key.id)}


@router.post("/fullenrich")
async def fullenrich_webhook(
    request: Request,
    workspace_id: str = Query(...),
    secret: str = Query(...),
    job_id: Optional[str] = Query(None),
    session: AsyncSession = Depends(get_async_session),
    redis_client: redis.Redis = Depends(get_redis_client),
):
    """Inbound webhook receiver for FullEnrich."""
    source_ip = request.client.host if request.client else None

    if not workspace_id or not secret:
        raise HTTPException(status_code=400, detail="workspace_id and secret are required")

    if job_id:
        try:
            job_uuid = UUID(str(job_id))
        except Exception:
            raise HTTPException(status_code=401, detail="Invalid webhook job_id")
        result = await session.execute(
            select(BYOEnrichmentJob).where(
                BYOEnrichmentJob.id == job_uuid,
                BYOEnrichmentJob.workspace_id == workspace_id,
            )
        )
        job = result.scalar_one_or_none()
        if not job:
            raise HTTPException(status_code=401, detail="Invalid webhook job_id")
        summary = job.summary or {}
        stored_hash = summary.get("fullenrich_webhook_secret_hash")
        if stored_hash:
            candidate = hashlib.sha256(secret.encode()).hexdigest()
            if not hmac.compare_digest(str(stored_hash), candidate):
                raise HTTPException(status_code=401, detail="Invalid webhook secret")
        else:
            backend = get_saas_secret_backend()
            stored = await backend.get_secret(
                db=session,
                integration="fullenrich",
                key="webhook_secret",
                workspace_id=workspace_id,
            )
            if not stored or not hmac.compare_digest(stored.value, secret):
                raise HTTPException(status_code=401, detail="Invalid webhook secret")
    else:
        backend = get_saas_secret_backend()
        stored = await backend.get_secret(
            db=session,
            integration="fullenrich",
            key="webhook_secret",
            workspace_id=workspace_id,
        )
        if not stored or not hmac.compare_digest(stored.value, secret):
            raise HTTPException(status_code=401, detail="Invalid webhook secret")

    body = await request.body()
    try:
        payload = json.loads(body)
        data_format = "json"
    except Exception:
        raise HTTPException(status_code=400, detail="Invalid JSON payload")

    inbound = InboundWebhook(
        account_id=workspace_id,
        source="fullenrich",
        source_ip=source_ip,
        user_agent=request.headers.get("User-Agent"),
        method=request.method,
        path=str(request.url.path),
        headers=dict(request.headers),
        query_params=dict(request.query_params),
        payload=payload,
        raw_body=body.decode("utf-8")[:10000],
        content_type=request.headers.get("Content-Type"),
        payload_size=len(body),
        status="received",
        data_format=data_format,
        integration_id="fullenrich",
    )

    session.add(inbound)
    await session.flush()

    await redis_client.lpush(
        "webhook:fullenrich:queue",
        json.dumps(
            {
                "id": str(inbound.id),
                "workspace_id": workspace_id,
            }
        ),
    )

    await session.commit()

    return JSONResponse(
        status_code=202,
        content={
            "status": "accepted",
            "id": str(inbound.id),
            "message": "FullEnrich webhook received",
        },
    )


@router.post("/gmail-push")
async def gmail_push_webhook(
    request: Request,
    session: AsyncSession = Depends(get_async_session),
    redis_client: redis.Redis = Depends(get_redis_client),
):
    token = request.headers.get("X-Gmail-Webhook-Token") or request.query_params.get(
        "token"
    )
    expected = os.getenv("GMAIL_PUBSUB_TOKEN")
    if expected:
        if token != expected:
            raise HTTPException(status_code=401, detail="Invalid Gmail webhook token")
    elif settings.IS_PRODUCTION:
        raise HTTPException(
            status_code=503, detail="Gmail webhook token not configured"
        )

    payload = await request.json()
    message = payload.get("message") or {}
    data_b64 = message.get("data")
    attributes = message.get("attributes") or {}

    decoded: Dict[str, Any] = {}
    if data_b64:
        try:
            decoded = json.loads(base64.b64decode(data_b64).decode("utf-8"))
        except Exception as exc:
            logger.warning("Failed to decode Gmail push payload: %s", exc)

    email_address = decoded.get("emailAddress") or attributes.get("emailAddress")
    history_id = decoded.get("historyId") or attributes.get("historyId")
    if not email_address or not history_id:
        raise HTTPException(status_code=400, detail="Missing emailAddress/historyId")

    mailbox = await ensure_mailbox_integration(session, email_address)

    if not mailbox.last_history_id:
        mailbox.last_history_id = str(history_id)
        session.add(mailbox)
        await session.commit()
        return {"ok": True, "data": {"status": "initialized", "mailbox_id": mailbox.id}}

    await redis_client.lpush(
        "gmail:history:queue",
        json.dumps(
            {
                "mailbox_id": mailbox.id,
                "history_id": str(history_id),
                "email": email_address,
            }
        ),
    )

    return {"ok": True, "data": {"status": "queued", "mailbox_id": mailbox.id}}


@router.post("/gmail-watch-renew")
async def gmail_watch_renew(
    request: Request,
    session: AsyncSession = Depends(get_async_session),
):
    """
    Renew Gmail push notification watch.
    Called by Cloud Scheduler every ~6 days to prevent watch expiration.
    """
    token = request.headers.get("X-Gmail-Webhook-Token") or request.query_params.get(
        "token"
    )
    expected = os.getenv("GMAIL_PUBSUB_TOKEN")
    if expected:
        if token != expected:
            raise HTTPException(status_code=401, detail="Invalid Gmail webhook token")
    elif settings.IS_PRODUCTION:
        raise HTTPException(
            status_code=503, detail="Gmail webhook token not configured"
        )

    topic = os.getenv("GMAIL_WATCH_TOPIC")
    if not topic:
        raise HTTPException(status_code=503, detail="GMAIL_WATCH_TOPIC not configured")

    try:
        gmail = await GmailSupportService.from_secret(session)
        response = await gmail.register_watch(topic_name=topic, label_ids=["INBOX"])

        mailbox = await ensure_mailbox_integration(session, gmail.support_email)
        history_id = response.get("historyId")
        if history_id:
            mailbox.last_history_id = str(history_id)
            mailbox.updated_at = datetime.utcnow()
            session.add(mailbox)
            await session.commit()

        expiration = response.get("expiration")
        logger.info("Gmail watch renewed: historyId=%s expiration=%s", history_id, expiration)

        return {
            "ok": True,
            "data": {
                "historyId": history_id,
                "expiration": expiration,
                "email": gmail.support_email,
            },
        }
    except Exception as exc:
        logger.exception("Gmail watch renewal failed")
        raise HTTPException(status_code=500, detail=str(exc)) from exc


@router.post("/{integration_id}")
async def custom_webhook(
    integration_id: str,
    request: Request,
    session: AsyncSession = Depends(get_async_session),
    redis_client: redis.Redis = Depends(get_redis_client),
    api_key: Optional[str] = Depends(get_api_key),
):
    """
    Customer-specific webhook endpoint.

    Defined after management routes to prevent path collisions
    (e.g., /api-keys, /endpoints, /deliveries).
    """
    # Guard against management routes being captured here
    if integration_id in {"api-keys", "endpoints", "deliveries", "sheets"}:
        raise HTTPException(status_code=404, detail="Not Found")

    source_ip = request.client.host if request.client else None

    # Verify API key if provided
    account_id = None
    if api_key:
        key_record = await verify_api_key(api_key, session, source_ip)
        if key_record:
            account_id = key_record.account_id

    # Get request body
    body = await request.body()

    # Try to parse as JSON
    try:
        payload = json.loads(body)
        data_format = "json"
    except Exception:
        payload = {"raw": body.decode("utf-8")}
        data_format = "raw"

    # Create inbound webhook record
    inbound = InboundWebhook(
        account_id=account_id,
        source="custom",
        source_ip=source_ip,
        user_agent=request.headers.get("User-Agent"),
        method=request.method,
        path=str(request.url.path),
        headers=dict(request.headers),
        query_params=dict(request.query_params),
        payload=payload,
        raw_body=body.decode("utf-8")[:10000],
        content_type=request.headers.get("Content-Type"),
        payload_size=len(body),
        status="received",
        data_format=data_format,
        integration_id=integration_id,
    )

    session.add(inbound)
    await session.flush()

    # Queue for processing based on integration_id
    await redis_client.lpush(
        f"webhook:custom:{integration_id}",
        json.dumps(
            {
                "id": str(inbound.id),
                "account_id": account_id,
                "integration_id": integration_id,
                "format": data_format,
            }
        ),
    )

    await session.commit()

    return JSONResponse(
        status_code=202,
        content={
            "status": "accepted",
            "id": str(inbound.id),
            "message": f"Webhook received for integration {integration_id}",
        },
    )
