"""
FastAPI router to receive Payrix Web Alerts (webhook events).

Validates a shared secret header from env, parses payload, performs
idempotency dedup, and creates or updates internal transaction records.
"""
from __future__ import annotations

import hashlib
import json
import logging
from typing import Any, Dict, Optional
from datetime import datetime, timezone

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

from src.core.config import settings
from src.core.database import get_session
from src.core.exceptions import UnauthorizedError, ValidationException, NotFoundError
from src.apps.transactions.models.transactions import Transactions
from src.core.utils.enums import TransactionStatusTypes, TransactionCategories, TransactionTypes
from src.core.payrix.status_mapper import map_payrix_payload_to_transaction_status
from src.apps.base.utils.functions import generate_secure_id

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


def _validate_secret(request: Request) -> None:
    name = settings.PAYRIX_WEBHOOK_HEADER_NAME
    expected = settings.PAYRIX_WEBHOOK_HEADER_VALUE
    if not name or not expected:
        # If not configured, reject to avoid insecure default
        raise UnauthorizedError(message="Payrix webhook secret not configured")
    received = request.headers.get(name)
    if received != expected:
        raise UnauthorizedError(message="Invalid Payrix webhook secret header")


def _extract_event_id(body: Dict[str, Any]) -> str:
    for key in ("eventId", "alertId", "id"):
        if key in body and isinstance(body[key], str):
            return body[key]
    # Fallback to stable hash of canonicalized payload
    canonical = json.dumps(body, sort_keys=True, separators=(",", ":"))
    return hashlib.sha256(canonical.encode("utf-8")).hexdigest()


def _extract_txn_id(body: Dict[str, Any]) -> Optional[str]:
    # Try top-level and common nested structures
    for key in ("txnId", "transactionId", "id", "txn_id"):
        val = body.get(key)
        if isinstance(val, str):
            return val
    txn = body.get("txn") or body.get("transaction") or {}
    for key in ("id", "txnId", "transactionId"):
        val = txn.get(key)
        if isinstance(val, str):
            return val
    return None


def _map_to_internal_status(payrix_status: Optional[str]) -> TransactionStatusTypes:
    s = (payrix_status or "").lower()
    if s in {"captured"}:
        return TransactionStatusTypes.CAPTURED
    if s in {"approved", "paid", "succeeded", "success"}:
        return TransactionStatusTypes.PAID
    if s in {"failed", "declined", "error"}:
        return TransactionStatusTypes.FAILED
    return TransactionStatusTypes.PENDING


@router.post("/payrix")
async def payrix_webalert_endpoint(request: Request, db: Session = Depends(get_session)):
    _validate_secret(request)
    try:
        payload: Dict[str, Any] = await request.json()
    except Exception:
        raise ValidationException(message="Invalid JSON payload", module="payrix")

    event_id = _extract_event_id(payload)
    txn_id = _extract_txn_id(payload)
    if not txn_id:
        raise ValidationException(message="Missing transaction id in payload", module="payrix")

    # Try to find existing transaction
    txn: Transactions | None = db.execute(
        select(Transactions).where(Transactions.txn_id == txn_id)
    ).scalar_one_or_none()
    
    # Create transaction if it doesn't exist
    if not txn:
        logger.info(
            "provider=payrix op=%s txn_id=%s creating_new_transaction=1",
            "webalert",
            txn_id,
        )
        
        # Extract transaction data from webhook payload
        txn_data = payload.get("txn") or payload.get("transaction") or payload
        amount = float(txn_data.get("total", 0)) / 100.0 if txn_data.get("total") else 0.0
        currency = txn_data.get("currency", "usd").upper()
        
        # Get merchant, customer, payment_request from metadata if available
        merchant_id = txn_data.get("merchant_id") or txn_data.get("merchant", {}).get("id")
        customer_id = txn_data.get("customer_id") or txn_data.get("customer", {}).get("id")
        payment_request_id = txn_data.get("payment_request_id")
        
        # Create new transaction record
        txn = Transactions(
            txn_id=txn_id,
            txn_amount=amount,
            currency=currency,
            txn_type=txn_data.get("type"),
            txn_status=map_payrix_payload_to_transaction_status(payload),
            reference_id=txn_data.get("reference_id") or generate_secure_id(prepend="ref", length=20),
            description=txn_data.get("description"),
            category=TransactionCategories.CHARGE,
            transaction_type=TransactionTypes.PAYMENT_TERMINAL,
            txn_metadata={
                "payrix_events": [event_id],
                "last_payrix_webalert": payload,
                "created_from_webhook": True,
            },
            merchant_id=merchant_id,
            customer_id=customer_id,
            payment_request_id=payment_request_id,
            ocurred_at=datetime.now(timezone.utc),
        )
        db.add(txn)
        db.commit()
        db.refresh(txn)
        
        logger.info(
            "provider=payrix op=%s txn_id=%s event_id=%s status=%s created=1",
            "webalert",
            txn_id,
            event_id,
            txn.txn_status,
        )
        return {"ok": True, "created": True}

    # Idempotency: check if we've processed this event id
    meta = txn.txn_metadata or {}
    processed = meta.get("payrix_events") or []
    if event_id in processed:
        logger.info(
            "provider=payrix op=%s txn_id=%s event_id=%s idempotent_hit=1",
            "webalert",
            txn_id,
            event_id,
        )
        return {"ok": True, "idempotent": True}

    # Update status
    internal_status = map_payrix_payload_to_transaction_status(payload)
    txn.txn_status = internal_status

    # Save payload and mark processed
    processed.append(event_id)
    meta["payrix_events"] = processed
    meta["last_payrix_webalert"] = payload
    txn.txn_metadata = meta

    db.add(txn)
    db.commit()

    logger.info(
        "provider=payrix op=%s txn_id=%s event_id=%s status=%s updated=1",
        "webalert",
        txn_id,
        event_id,
        internal_status,
    )
    return {"ok": True, "updated": True}
