"""
Merchant webhook dispatcher for Cart Plugin payment events.

Sends a signed HTTP POST to the merchant's configured webhook_url.
Never raises — payment processing must not be blocked by webhook failures.
"""
from __future__ import annotations

import hashlib
import hmac
import ipaddress
import json
import logging
import socket
import time
from typing import TYPE_CHECKING
from urllib.parse import urlparse

import httpx

from src.apps.payment_providers.helpers.credentials import decrypt_credential

if TYPE_CHECKING:
    from sqlalchemy.orm import Session
    from src.apps.cart_plugin.models.cart_session import CartSession

logger = logging.getLogger(__name__)

WEBHOOK_TIMEOUT_SECONDS = 5


def _validate_webhook_url(url: str) -> bool:
    """
    Validate that the webhook URL is a safe public HTTPS endpoint.
    Blocks private network, loopback, link-local, and non-HTTPS URLs.
    """
    try:
        parsed = urlparse(url)
        if parsed.scheme != "https":
            return False
        hostname = parsed.hostname
        if not hostname:
            return False
        # Resolve hostname to IP
        ip_str = socket.gethostbyname(hostname)
        ip = ipaddress.ip_address(ip_str)
        # Block private, loopback, link-local, reserved ranges
        if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved:
            return False
        return True
    except Exception:
        return False


async def dispatch_merchant_webhook(
    webhook_url: str,
    webhook_secret_encrypted: str,
    payload: dict,
    session: "CartSession",
    db: "Session",
) -> None:
    """
    POST a signed webhook to the merchant's endpoint.

    Signature format (Stripe-compatible):
        signed_payload = f"{unix_ts}.{json_body}"
        header: X-HubWallet-Signature: t={ts},v1={hmac_hex}

    On any failure: logs the error and records the HTTP status (or 0 for
    connection errors) on the session.  Never raises.
    """
    # CRIT-01: Validate webhook URL before making any outbound request.
    if not _validate_webhook_url(webhook_url):
        logger.warning(
            "cart_plugin webhook: blocked unsafe or non-HTTPS webhook URL for session %s url=%s",
            session.token,
            webhook_url,
        )
        return

    status_code = 0
    try:
        raw_secret = decrypt_credential(webhook_secret_encrypted)

        json_body = json.dumps(payload, separators=(",", ":"), default=str)
        ts = int(time.time())
        signed_payload = f"{ts}.{json_body}"

        # CRIT-02: HMAC uses digestmod=hashlib.sha256 (the class, not a string).
        signature = hmac.new(
            key=raw_secret.encode("utf-8"),
            msg=signed_payload.encode("utf-8"),
            digestmod=hashlib.sha256,
        ).hexdigest()

        headers = {
            "Content-Type": "application/json",
            "X-HubWallet-Signature": f"t={ts},v1={signature}",
        }

        # CRIT-01: follow_redirects=False prevents redirect-based SSRF bypasses.
        async with httpx.AsyncClient(
            timeout=WEBHOOK_TIMEOUT_SECONDS, follow_redirects=False
        ) as client:
            response = await client.post(webhook_url, content=json_body, headers=headers)
            status_code = response.status_code

            if response.status_code >= 400:
                logger.warning(
                    "cart_plugin webhook: non-2xx response %d for session %s url=%s",
                    response.status_code,
                    session.token,
                    webhook_url,
                )

    except Exception as exc:
        # CRIT-02: Log exception type so AttributeError / misconfiguration is visible.
        logger.error(
            "cart_plugin webhook: delivery failed for session %s url=%s [%s]: %s",
            session.token,
            webhook_url,
            type(exc).__name__,
            exc,
        )

    # Best-effort: record the result.  If this fails we still don't raise.
    try:
        from src.apps.cart_plugin import crud as cart_crud

        cart_crud.update_cart_session_webhook_status(db, session, status_code)
        db.commit()
    except Exception as exc:
        logger.error(
            "cart_plugin webhook: failed to persist webhook status for session %s: %s",
            session.token,
            exc,
        )
