"""
ABOS AI Worker – Background Task: Webhook Delivery (Unified Finance)
=====================================================================
Sends verified, human-approved or auto-validated statement data to the
Spring Boot backend via an outbound HTTP POST.

Webhook contract
----------------
POST  <spring_boot_webhook_url>
Headers:
    Content-Type       : application/json
    X-Event            : PROCESS_AI_BANK_STATEMENT
    X-Webhook-Secret   : <shared secret from environment>
    X-Idempotency-Key  : <document_id>
Body:
    AiExtractionWebhookPayload JSON
"""

from __future__ import annotations

import asyncio
import logging
from typing import Any

import httpx

from app.config import settings
from app.schemas import AiExtractionWebhookPayload, StatementResult, EntityType
from app.store import document_store

logger = logging.getLogger(__name__)

MAX_RETRIES = 3
BASE_BACKOFF_SECONDS = 2.0


async def deliver_webhook(
    *,
    document_id: str,
    verified_data: StatementResult,
    spring_boot_webhook_url: str,
    tenant_id: str,
    user_id: str,
    entity_type: EntityType,
    entity_id: int | None = None,
) -> None:
    """
    Fire-and-forget webhook delivery using exponential backoff.
    Cleans up transient storage upon completion or permanent failure.
    """
    # ── 1. Construct outbound payload using Pydantic model ─────────────────
    payload_model = AiExtractionWebhookPayload(
        tenantId=tenant_id,
        userId=user_id,
        documentId=document_id,
        entityType=entity_type,
        entityId=entity_id,
        bankStatementData=verified_data
    )
    
    # Serialise model to dict containing raw primitive values (handling Decimals)
    payload_dict = payload_model.model_dump(by_alias=True, mode="json")

    # ── 2. Configure HTTP headers ─────────────────────────────────────────
    # Read webhook secret from settings (uses app_name or general token if not set)
    webhook_secret = getattr(settings, "webhook_secret", "abos-default-secret-token")

    headers = {
        "Content-Type": "application/json",
        "X-Event": "PROCESS_AI_BANK_STATEMENT",
        "X-Webhook-Secret": webhook_secret,
        "X-Idempotency-Key": document_id,
    }

    delivered = False
    last_error: Exception | None = None

    async with httpx.AsyncClient(timeout=settings.webhook_timeout) as client:
        for attempt in range(1, MAX_RETRIES + 1):
            try:
                logger.info(
                    "[Webhook] Attempt %d/%d → %s (tenant=%s, user=%s, doc=%s)",
                    attempt,
                    MAX_RETRIES,
                    spring_boot_webhook_url,
                    tenant_id,
                    user_id,
                    document_id
                )
                response = await client.post(
                    spring_boot_webhook_url,
                    headers=headers,
                    json=payload_dict,
                )
                response.raise_for_status()
                logger.info(
                    "[Webhook] Delivered successfully (HTTP %d) on attempt %d.",
                    response.status_code,
                    attempt,
                )
                delivered = True
                break

            except (httpx.HTTPStatusError, httpx.RequestError) as exc:
                last_error = exc
                logger.warning(
                    "[Webhook] Attempt %d failed: %s",
                    attempt,
                    exc,
                )
                if attempt < MAX_RETRIES:
                    backoff = BASE_BACKOFF_SECONDS * (2 ** (attempt - 1))
                    logger.info("[Webhook] Retrying in %.1f s …", backoff)
                    await asyncio.sleep(backoff)

    # ── 3. Handle Job Status & Eviction ───────────────────────────────────
    record = await document_store.get(document_id)
    if record:
        if delivered:
            record.status = "COMPLETED"
        else:
            record.status = "FAILED"
            record.error_detail = f"Webhook delivery failed after {MAX_RETRIES} attempts. Error: {last_error}"
        await document_store.update(record)

    # Delete from store to prevent memory leaks/replays as requested
    await document_store.delete(document_id)
    logger.info("[Store] document_id=%s evicted from transient store.", document_id)
