"""Webhook delivery service (task 3.7.3).

Public API
----------
``compute_hmac(secret, body)``
    Pure function — returns ``"sha256=<hex>"`` signature string.

``deliver_webhook(endpoint, event_name, payload)``
    Create a ``WebhookDelivery`` row and enqueue ``send_webhook_task``.
    Returns the delivery instance.  Fast and non-blocking.

``do_send(delivery)``
    Make the actual HTTP POST.  Returns ``(success, status_code, body)``.
    Called from ``send_webhook_task``; never raises.

Constants
---------
``RETRY_DELAYS``
    Seconds between consecutive retry attempts: 1m, 5m, 30m, 2h, 12h.

``MAX_FAILURES``
    Consecutive endpoint failures before auto-deactivation.
"""

from __future__ import annotations

import hashlib
import hmac
import json
import urllib.error
import urllib.request
from typing import Any

import structlog

_log = structlog.get_logger("simorgh.automation.webhook")

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------

RETRY_DELAYS: list[int] = [60, 300, 1800, 7200, 43200]  # 1m 5m 30m 2h 12h
MAX_FAILURES: int = 5


# ---------------------------------------------------------------------------
# HMAC helper
# ---------------------------------------------------------------------------

def compute_hmac(secret: str, body: bytes) -> str:
    """Return ``'sha256=<hex>'`` HMAC-SHA256 for *body* signed with *secret*."""
    digest = hmac.new(secret.encode("utf-8"), body, hashlib.sha256).hexdigest()
    return f"sha256={digest}"


# ---------------------------------------------------------------------------
# Delivery creation (fan-out entry point)
# ---------------------------------------------------------------------------

def deliver_webhook(
    endpoint: Any,
    event_name: str,
    payload: dict[str, Any],
) -> Any:
    """Create a ``WebhookDelivery`` row and enqueue ``send_webhook_task``.

    This is the *only* public function that should be called from the Event
    Bus handler.  It is intentionally thin: no HTTP I/O happens here.

    Parameters
    ----------
    endpoint:
        A ``WebhookEndpoint`` instance.
    event_name:
        The Event Bus event name, e.g. ``"crm.lead.created"``.
    payload:
        The event payload dict.

    Returns
    -------
    WebhookDelivery
        The freshly created delivery row (status=``pending``).
    """
    from simorgh.apps.automation.models import DeliveryStatus, WebhookDelivery
    from simorgh.apps.automation.tasks import send_webhook_task

    delivery = WebhookDelivery.objects.create(
        tenant=endpoint.tenant,
        organization_node=endpoint.organization_node,
        endpoint=endpoint,
        event_name=event_name,
        payload=payload,
        status=DeliveryStatus.PENDING,
        attempt=0,
    )

    try:
        send_webhook_task.delay(delivery.pk)
    except Exception as exc:
        _log.warning(
            "webhook.enqueue_failed",
            delivery_id=delivery.pk,
            endpoint_id=endpoint.pk,
            error=str(exc),
        )

    return delivery


# ---------------------------------------------------------------------------
# HTTP send (called from the Celery task)
# ---------------------------------------------------------------------------

def do_send(delivery: Any) -> tuple[bool, int | None, str]:
    """Perform the HTTP POST for *delivery*.

    Returns
    -------
    (success, status_code, response_body)
        ``success`` is ``True`` iff the server returned a 2xx status.
        ``status_code`` is ``None`` when a network-level error occurs
        (connection refused, timeout, etc.).
        ``response_body`` is at most 4 KB of the server response (or the
        exception string on network errors).

    Never raises — all exceptions are caught and returned as a failure tuple.
    """
    endpoint = delivery.endpoint
    body = json.dumps(delivery.payload, ensure_ascii=False, default=str).encode("utf-8")

    # Build headers: custom overrides first, then standard Simorgh headers.
    headers: dict[str, str] = {}
    for k, v in (endpoint.headers or {}).items():
        headers[str(k)] = str(v)

    headers.update(
        {
            "Content-Type": "application/json; charset=utf-8",
            "X-Simorgh-Event": delivery.event_name,
            "X-Simorgh-Delivery": str(delivery.public_id),
        }
    )

    if endpoint.secret:
        headers["X-Simorgh-Signature"] = compute_hmac(endpoint.secret, body)

    req = urllib.request.Request(
        url=endpoint.url,
        data=body,
        headers=headers,
        method="POST",
    )

    try:
        with urllib.request.urlopen(req, timeout=endpoint.timeout_seconds) as resp:
            resp_body = resp.read(4096).decode("utf-8", errors="replace")
            return True, resp.status, resp_body
    except urllib.error.HTTPError as exc:
        resp_body = ""
        if exc.fp:
            try:
                resp_body = exc.fp.read(4096).decode("utf-8", errors="replace")
            except Exception:
                pass
        return False, exc.code, resp_body
    except Exception as exc:
        return False, None, str(exc)
