"""
Dynamic webhook dispatcher. Routes incoming webhooks to the correct provider handler.
"""
import json
import logging
import re

from fastapi import APIRouter, Depends, Request, HTTPException
from sqlalchemy.orm import Session

from src.core.database import get_db
from src.core.providers.registry import ProviderRegistry
from src.core.providers.base import ProviderConfig
from src.core.config import settings

router = APIRouter()
logger = logging.getLogger("webhooks")

# Allowlist pattern for provider slugs arriving via the URL path parameter.
# Slugs are defined by the admin and must match the same pattern enforced at
# provider creation time (AdminCreateProviderRequest.slug pattern).
_SLUG_RE = re.compile(r'^[a-z][a-z0-9_]{0,49}$')


@router.post("/{provider_slug}")
async def dynamic_webhook_handler(
    provider_slug: str,
    request: Request,
    db: Session = Depends(get_db),
):
    """
    Dynamic webhook dispatcher. Routes incoming webhooks to the correct provider handler.
    Keeps /webhooks/payrix as a legacy alias (registered first in core/router.py).
    """
    # Sanitise the path parameter before any DB or registry lookup to prevent
    # path-traversal / injection via crafted slug values.
    if not _SLUG_RE.match(provider_slug):
        raise HTTPException(status_code=404, detail="Unknown provider")

    from sqlalchemy import select
    from src.apps.payment_providers.models.payment_provider import PaymentProvider

    # Look up provider in DB
    stmt = select(PaymentProvider).where(
        PaymentProvider.slug == provider_slug,
        PaymentProvider.is_active == True,
        PaymentProvider.deleted_at.is_(None),
    )
    payment_provider = db.execute(stmt).scalar_one_or_none()
    if not payment_provider:
        # Return a generic 404 — do not confirm whether the slug exists or not.
        raise HTTPException(status_code=404, detail="Unknown provider")

    if not ProviderRegistry.is_registered(provider_slug):
        raise HTTPException(status_code=404, detail="Unknown provider")

    # Read raw body BEFORE parsing JSON (needed for signature verification)
    raw_body = await request.body()
    headers = dict(request.headers)

    # Build the ProviderConfig used for signature verification.
    # For Payrix on the legacy global-secret path, source the secret from env vars.
    # For all other providers the global env-var secret is not applicable — the
    # webhook cannot be securely verified without per-provider credentials loaded
    # from the DB.  In that case we reject with 501 rather than silently skip
    # verification (which was the previous behaviour with webhook_secret="").
    if provider_slug == "payrix":
        webhook_secret = settings.PAYRIX_WEBHOOK_HEADER_VALUE or ""
        webhook_header_name = settings.PAYRIX_WEBHOOK_HEADER_NAME or ""
        config = ProviderConfig(
            provider_slug=provider_slug,
            credentials={
                "webhook_secret": webhook_secret,
            },
            config_data={
                "webhook_header_name": webhook_header_name,
            },
        )
    elif provider_slug == "tsys":
        webhook_secret = settings.TSYS_WEBHOOK_SECRET or ""
        config = ProviderConfig(
            provider_slug=provider_slug,
            credentials={"webhook_secret": webhook_secret},
            config_data={},
        )
    else:
        logger.warning(
            "op=dynamic_webhook_handler provider=%s result=no_global_secret action=rejected",
            provider_slug,
        )
        raise HTTPException(
            status_code=501,
            detail=f"Webhook endpoint for provider '{provider_slug}' is not yet configured.",
        )

    provider = ProviderRegistry.get(provider_slug)

    # Verify webhook signature — reject immediately on failure.
    if not provider.verify_webhook(config, headers, raw_body):
        logger.warning(
            "op=dynamic_webhook_handler provider=%s result=signature_mismatch ip=%s",
            provider_slug,
            request.client.host if request.client else "unknown",
        )
        raise HTTPException(status_code=401, detail="Webhook signature verification failed")

    # Parse payload
    try:
        payload = json.loads(raw_body)
    except json.JSONDecodeError:
        raise HTTPException(status_code=400, detail="Invalid JSON payload")

    event = provider.parse_webhook_event(payload)

    # Process the normalized event
    await _process_webhook_event(event, db)

    return {"ok": True}


async def _process_webhook_event(event: dict, db: Session) -> None:
    """Process a normalized webhook event — update transaction records."""
    from sqlalchemy import select
    from src.apps.transactions.models.transactions import Transactions
    from src.core.utils.enums import TransactionStatusTypes

    txn_id = event.get("transaction_id", "")
    event_id = event.get("event_id", "")
    status_str = event.get("status", "")

    if not txn_id:
        return

    # Map status string to enum
    status_map = {
        "succeeded": TransactionStatusTypes.PAID,
        "failed": TransactionStatusTypes.FAILED,
        "processing": TransactionStatusTypes.PENDING,
        "captured": TransactionStatusTypes.CAPTURED,
    }
    new_status = status_map.get(status_str, TransactionStatusTypes.PENDING)

    # Find existing transaction
    stmt = select(Transactions).where(Transactions.txn_id == txn_id)
    txn = db.execute(stmt).scalar_one_or_none()

    if txn:
        # Idempotency: check if event already processed
        metadata = txn.txn_metadata or {}
        processed_events = metadata.get("provider_events", [])
        if event_id and event_id in processed_events:
            logger.info("op=process_webhook_event txn_id=%s event_id=%s idempotent_hit=1", txn_id, event_id)
            return  # Already processed — idempotent

        txn.txn_status = new_status
        if event_id:
            processed_events.append(event_id)
            metadata["provider_events"] = processed_events
            txn.txn_metadata = metadata
        db.commit()
        logger.info("op=process_webhook_event txn_id=%s event_id=%s status=%s updated=1", txn_id, event_id, new_status)
