"""Webhook delivery with HMAC signing, retry, and delivery tracking."""

from __future__ import annotations

import hashlib
import hmac
import json
from datetime import UTC, datetime

import httpx
import structlog

from hive_compute.coordinator.database import get_db_ctx, record_event
from hive_compute.coordinator.metrics import increment_webhook_delivery
from hive_compute.url_safety import validate_outbound_http_url

logger = structlog.get_logger()

MAX_RETRY_ATTEMPTS = 5
RETRY_DELAYS = [10, 30, 120, 600, 3600]  # 10s, 30s, 2m, 10m, 1h


def fire_webhook(job_id: str) -> None:
    """Send webhook notification if a callback_url is set for the job.

    Creates a tracked delivery record and attempts immediate delivery.
    """
    with get_db_ctx() as db:
        row = db.execute(
            "SELECT job_id, status, callback_url, result_data, error_message, partner_id FROM jobs WHERE job_id = ?",
            (job_id,),
        ).fetchone()

    if not row or not row["callback_url"]:
        return

    payload = {
        "job_id": row["job_id"],
        "status": row["status"],
        "timestamp": datetime.now(UTC).isoformat().replace("+00:00", "Z"),
    }

    if row["status"] == "completed" and row["result_data"]:
        payload["result"] = json.loads(row["result_data"])
    elif row["status"] == "failed":
        payload["error"] = row["error_message"]

    payload_json = json.dumps(payload, sort_keys=True)

    # Look up callback secret for HMAC signing
    signature = None
    if row["partner_id"]:
        with get_db_ctx() as db:
            partner = db.execute(
                "SELECT callback_secret FROM partners WHERE partner_id = ?",
                (row["partner_id"],),
            ).fetchone()
            if partner and partner["callback_secret"]:
                signature = _sign_payload(payload_json, partner["callback_secret"])

    # Create delivery record
    with get_db_ctx() as db:
        db.execute(
            """
            INSERT INTO webhook_deliveries (job_id, target_url, payload, signature, status)
            VALUES (?, ?, ?, ?, 'pending')
            """,
            (job_id, row["callback_url"], payload_json, signature),
        )

    # Attempt immediate delivery
    _attempt_delivery(job_id, row["callback_url"], payload_json, signature, job_status=row["status"])


def _sign_payload(payload: str, secret: str) -> str:
    """Create HMAC-SHA256 signature for webhook payload."""
    return hmac.new(secret.encode(), payload.encode(), hashlib.sha256).hexdigest()


def _attempt_delivery(
    job_id: str,
    url: str,
    payload: str,
    signature: str | None,
    job_status: str | None = None,
) -> bool:
    """Try to deliver a webhook. Updates delivery record with result."""
    # Resolve actual job status if not provided (retry/replay paths)
    if not job_status:
        with get_db_ctx() as db:
            row = db.execute("SELECT status FROM jobs WHERE job_id = ?", (job_id,)).fetchone()
            job_status = row["status"] if row else "unknown"
    headers = {
        "Content-Type": "application/json",
        "User-Agent": "HiveCompute/0.1",
    }
    if signature:
        headers["X-Hive-Signature"] = f"sha256={signature}"

    try:
        validate_outbound_http_url(url, purpose="webhook target")
        resp = httpx.post(url, content=payload, timeout=10, headers=headers)
        status_code = resp.status_code
        success = 200 <= status_code < 300
        increment_webhook_delivery(success=success)

        with get_db_ctx() as db:
            if success:
                db.execute(
                    """
                    UPDATE webhook_deliveries SET status = 'delivered', status_code = ?,
                           attempts = attempts + 1, delivered_at = datetime('now')
                    WHERE job_id = ? AND status IN ('pending', 'retrying')
                    """,
                    (status_code, job_id),
                )
                record_event(
                    db, job_id, "webhook_delivered", to_status=job_status,
                    actor="webhook", detail=f"HTTP {status_code} to {url[:60]}",
                )
            else:
                _schedule_retry(db, job_id, f"HTTP {status_code}", job_status=job_status)

        return success

    except Exception as e:
        increment_webhook_delivery(success=False)
        with get_db_ctx() as db:
            _schedule_retry(db, job_id, str(e)[:200], job_status=job_status)

        logger.warning("Webhook delivery failed", job_id=job_id, error=str(e))
        return False


def _schedule_retry(db, job_id: str, error: str, job_status: str = "unknown") -> None:
    """Schedule a retry for a failed webhook delivery."""
    row = db.execute(
        "SELECT attempts, max_attempts FROM webhook_deliveries WHERE job_id = ? ORDER BY delivery_id DESC LIMIT 1",
        (job_id,),
    ).fetchone()

    if not row:
        logger.warning("Webhook delivery record not found for retry", job_id=job_id)
        return

    new_attempts = row["attempts"] + 1
    if new_attempts >= row["max_attempts"]:
        db.execute(
            """
            UPDATE webhook_deliveries SET status = 'dead_letter', attempts = ?,
                   last_error = ? WHERE job_id = ? AND status IN ('pending', 'retrying')
            """,
            (new_attempts, error, job_id),
        )
        record_event(
            db, job_id, "webhook_dead_letter", to_status=job_status,
            actor="webhook", detail=f"exhausted {new_attempts} attempts: {error}",
        )
    else:
        delay = RETRY_DELAYS[min(new_attempts - 1, len(RETRY_DELAYS) - 1)]
        db.execute(
            """
            UPDATE webhook_deliveries SET status = 'retrying', attempts = ?,
                   next_retry_at = datetime('now', ?), last_error = ?
            WHERE job_id = ? AND status IN ('pending', 'retrying')
            """,
            (new_attempts, f"+{delay} seconds", error, job_id),
        )


def retry_pending_webhooks() -> int:
    """Process webhooks that are due for retry. Returns count attempted."""
    with get_db_ctx() as db:
        rows = db.execute(
            """
            SELECT job_id, target_url, payload, signature FROM webhook_deliveries
            WHERE status = 'retrying' AND next_retry_at <= datetime('now')
            """,
        ).fetchall()

    retried = 0
    for row in rows:
        _attempt_delivery(row["job_id"], row["target_url"], row["payload"], row["signature"])
        retried += 1

    return retried


def replay_webhook(job_id: str) -> bool:
    """Manually replay a dead-letter webhook."""
    with get_db_ctx() as db:
        row = db.execute(
            """
            SELECT target_url, payload, signature
            FROM webhook_deliveries
            WHERE job_id = ?
            ORDER BY delivery_id DESC
            LIMIT 1
            """,
            (job_id,),
        ).fetchone()

        if not row:
            return False

        db.execute(
            "UPDATE webhook_deliveries SET status = 'retrying', attempts = 0 WHERE job_id = ?",
            (job_id,),
        )

    return _attempt_delivery(job_id, row["target_url"], row["payload"], row["signature"])
