"""Checkout surface data models."""
from __future__ import annotations

import json
import uuid
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from decimal import Decimal
from enum import Enum
from typing import Any

# Session timeout reduced from 30 minutes to 15 minutes (audit fix #1)
DEFAULT_SESSION_TIMEOUT_MINUTES = 15
DEFAULT_PAYMENT_LINK_EXPIRATION_HOURS = 24


class PSPType(str, Enum):
    """Payment Service Provider types."""
    STRIPE = "stripe"
    PAYPAL = "paypal"
    COINBASE = "coinbase"
    CIRCLE = "circle"
    SARDIS = "sardis"


class PaymentStatus(str, Enum):
    """Payment status."""
    PENDING = "pending"
    PROCESSING = "processing"
    COMPLETED = "completed"
    FAILED = "failed"
    CANCELLED = "cancelled"
    REFUNDED = "refunded"
    PARTIALLY_PAID = "partially_paid"
    EXPIRED = "expired"


class PaymentMethod(str, Enum):
    """Supported payment methods."""
    CARD = "card"
    APPLE_PAY = "apple_pay"
    GOOGLE_PAY = "google_pay"
    KLARNA = "klarna"
    LINK = "link"
    PAYPAL = "paypal"


class CheckoutEventType(str, Enum):
    """Checkout analytics event types."""
    SESSION_CREATED = "checkout.session.created"
    SESSION_STARTED = "checkout.session.started"
    SESSION_COMPLETED = "checkout.session.completed"
    SESSION_EXPIRED = "checkout.session.expired"
    SESSION_ABANDONED = "checkout.session.abandoned"
    PAYMENT_INITIATED = "checkout.payment.initiated"
    PAYMENT_SUCCEEDED = "checkout.payment.succeeded"
    PAYMENT_FAILED = "checkout.payment.failed"
    PAYMENT_REFUNDED = "checkout.payment.refunded"
    PARTIAL_PAYMENT = "checkout.payment.partial"
    FRAUD_CHECK_PASSED = "checkout.fraud.passed"
    FRAUD_CHECK_FAILED = "checkout.fraud.failed"
    FRAUD_CHECK_REVIEW = "checkout.fraud.review"
    WEBHOOK_RECEIVED = "checkout.webhook.received"
    WEBHOOK_PROCESSED = "checkout.webhook.processed"
    CURRENCY_CONVERTED = "checkout.currency.converted"


class FraudRiskLevel(str, Enum):
    """Fraud risk assessment levels."""
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    CRITICAL = "critical"


class FraudDecision(str, Enum):
    """Fraud check decision."""
    # Legacy decision label used in older integrations/tests.
    ALLOW = "allow"
    APPROVE = "approve"
    REVIEW = "review"
    DECLINE = "decline"
    CHALLENGE = "challenge"


class WebhookDeliveryStatus(str, Enum):
    """Webhook delivery status."""
    PENDING = "pending"
    DELIVERED = "delivered"
    FAILED = "failed"
    RETRYING = "retrying"


@dataclass
class CheckoutRequest:
    """Request to create a checkout session."""
    agent_id: str
    wallet_id: str
    mandate_id: str
    amount: Decimal
    currency: str = "USD"
    description: str | None = None
    success_url: str | None = None
    cancel_url: str | None = None
    chain: str | None = None  # For on-chain mode
    token: str | None = None  # For on-chain mode
    metadata: dict[str, Any] = field(default_factory=dict)
    # Idempotency support (audit fix #2)
    idempotency_key: str | None = None
    # Session timeout (audit fix #1)
    session_timeout_minutes: int = DEFAULT_SESSION_TIMEOUT_MINUTES
    # Partial payment support
    allow_partial_payment: bool = False
    minimum_payment_amount: Decimal | None = None
    # Multi-currency support
    accepted_currencies: list[str] = field(default_factory=lambda: ["USD"])
    auto_convert_currency: bool = False
    # Payment method selection
    payment_methods: list[str] = field(default_factory=lambda: ["card", "apple_pay", "google_pay", "link"])
    # Customization options
    customization: CheckoutCustomization | None = None
    # Customer session
    customer_id: str | None = None
    customer_email: str | None = None
    # Payment link
    create_payment_link: bool = False
    payment_link_expiration_hours: int = DEFAULT_PAYMENT_LINK_EXPIRATION_HOURS


@dataclass
class CheckoutResponse:
    """Checkout response from PSP."""
    checkout_id: str
    redirect_url: str | None = None
    status: PaymentStatus = PaymentStatus.PENDING
    psp_name: str | None = None
    amount: Decimal = Decimal("0")
    currency: str = "USD"
    agent_id: str = ""
    mandate_id: str = ""
    metadata: dict[str, Any] = field(default_factory=dict)
    created_at: datetime = field(default_factory=datetime.utcnow)
    expires_at: datetime | None = None
    completed_at: datetime | None = None
    psp_payment_id: str | None = None  # PSP's payment ID
    # Idempotency support
    idempotency_key: str | None = None
    is_duplicate: bool = False
    # Partial payment support
    amount_paid: Decimal = Decimal("0")
    amount_remaining: Decimal = Decimal("0")
    partial_payments: list[PartialPayment] = field(default_factory=list)
    # Multi-currency
    original_currency: str | None = None
    original_amount: Decimal | None = None
    exchange_rate: Decimal | None = None
    # Payment link
    payment_link_url: str | None = None
    payment_link_id: str | None = None
    payment_link_expires_at: datetime | None = None
    # Fraud check result
    fraud_check_result: FraudCheckResult | None = None
    # Customer session
    customer_session_id: str | None = None


@dataclass
class CheckoutSession:
    """Checkout session created by PSP (legacy, for backwards compatibility)."""
    session_id: str
    psp: PSPType
    checkout_url: str
    status: PaymentStatus = PaymentStatus.PENDING
    amount: Decimal = Decimal("0")
    currency: str = "USD"
    agent_id: str = ""
    merchant_id: str = ""
    metadata: dict[str, Any] = field(default_factory=dict)
    created_at: datetime = field(default_factory=datetime.utcnow)
    expires_at: datetime | None = None
    completed_at: datetime | None = None
    psp_payment_id: str | None = None  # PSP's payment ID
    payment_methods: list[str] = field(default_factory=lambda: ["card"])


@dataclass
class PSPConfig:
    """PSP configuration."""
    psp: PSPType
    enabled: bool = True
    api_key: str | None = None
    webhook_secret: str | None = None
    metadata: dict[str, Any] = field(default_factory=dict)


@dataclass
class MerchantConfig:
    """Merchant configuration."""
    merchant_id: str
    preferred_psps: list[PSPType] = field(default_factory=list)
    psp_configs: dict[PSPType, PSPConfig] = field(default_factory=dict)
    default_currency: str = "USD"
    metadata: dict[str, Any] = field(default_factory=dict)


# Idempotency support models (audit fix #2)
@dataclass
class IdempotencyRecord:
    """Record for tracking idempotent operations."""
    idempotency_key: str
    operation: str
    request_hash: str
    response: dict[str, Any] | None = None
    status: str = "pending"  # pending, completed, failed
    created_at: datetime = field(default_factory=datetime.utcnow)
    completed_at: datetime | None = None
    expires_at: datetime = field(
        default_factory=lambda: datetime.utcnow() + timedelta(hours=24)
    )
    agent_id: str | None = None
    checkout_id: str | None = None
    # Backward-compatible fields expected by older tests/clients.
    response_code: int | None = None
    response_body: str | None = None

    def __post_init__(self) -> None:
        # Legacy shape -> canonical shape.
        if self.response is None and self.response_body is not None:
            try:
                self.response = json.loads(self.response_body)
            except Exception:
                self.response = {"raw": self.response_body}

        # Canonical shape -> legacy fields.
        if self.response is not None and self.response_body is None:
            try:
                self.response_body = json.dumps(self.response, default=str)
            except Exception:
                self.response_body = str(self.response)

        if self.response_code is None and self.status == "completed" and self.response is not None:
            self.response_code = 200


# Analytics models (audit fix #3)
@dataclass
class CheckoutAnalyticsEvent:
    """Checkout analytics event for tracking."""
    event_id: str = field(default_factory=lambda: str(uuid.uuid4()))
    event_type: CheckoutEventType = CheckoutEventType.SESSION_CREATED
    checkout_id: str | None = None
    session_id: str | None = None
    agent_id: str | None = None
    customer_id: str | None = None
    psp_name: str | None = None
    amount: Decimal | None = None
    currency: str | None = None
    status: str | None = None
    error_code: str | None = None
    error_message: str | None = None
    metadata: dict[str, Any] = field(default_factory=dict)
    timestamp: datetime = field(default_factory=datetime.utcnow)
    duration_ms: int | None = None
    ip_address: str | None = None
    user_agent: str | None = None
    device_fingerprint: str | None = None


# Partial payment models
@dataclass
class PartialPayment:
    """Record of a partial payment."""
    payment_id: str = field(default_factory=lambda: str(uuid.uuid4()))
    checkout_id: str = ""
    amount: Decimal = Decimal("0")
    currency: str = "USD"
    psp_payment_id: str | None = None
    status: PaymentStatus = PaymentStatus.COMPLETED
    created_at: datetime = field(default_factory=datetime.utcnow)
    metadata: dict[str, Any] = field(default_factory=dict)


# Multi-currency models
@dataclass
class CurrencyConversion:
    """Currency conversion record."""
    conversion_id: str = field(default_factory=lambda: str(uuid.uuid4()))
    from_currency: str = "USD"
    to_currency: str = "USD"
    from_amount: Decimal = Decimal("0")
    to_amount: Decimal = Decimal("0")
    exchange_rate: Decimal = Decimal("1")
    rate_source: str = "internal"
    rate_timestamp: datetime = field(default_factory=datetime.utcnow)
    fee_amount: Decimal = Decimal("0")
    fee_currency: str = "USD"


@dataclass
class SupportedCurrency:
    """Supported currency configuration."""
    code: str  # ISO 4217 code
    name: str
    symbol: str
    decimal_places: int = 2
    min_amount: Decimal = Decimal("0.01")
    max_amount: Decimal = Decimal("1000000")
    enabled: bool = True


# Customer session models
@dataclass
class CustomerSession:
    """Customer checkout session."""
    session_id: str = field(default_factory=lambda: str(uuid.uuid4()))
    customer_id: str | None = None
    customer_email: str | None = None
    agent_id: str = ""
    status: str = "active"  # active, completed, expired, abandoned
    created_at: datetime = field(default_factory=datetime.utcnow)
    expires_at: datetime = field(
        default_factory=lambda: datetime.utcnow() + timedelta(minutes=DEFAULT_SESSION_TIMEOUT_MINUTES)
    )
    last_activity_at: datetime = field(default_factory=datetime.utcnow)
    checkout_ids: list[str] = field(default_factory=list)
    metadata: dict[str, Any] = field(default_factory=dict)
    ip_address: str | None = None
    user_agent: str | None = None
    device_fingerprint: str | None = None


# Payment link models
@dataclass
class PaymentLink:
    """Payment link for sharing."""
    link_id: str = field(default_factory=lambda: str(uuid.uuid4()))
    checkout_id: str = ""
    url: str = ""
    short_url: str | None = None
    status: str = "active"  # active, used, expired, revoked
    created_at: datetime = field(default_factory=datetime.utcnow)
    expires_at: datetime = field(
        default_factory=lambda: datetime.utcnow() + timedelta(hours=DEFAULT_PAYMENT_LINK_EXPIRATION_HOURS)
    )
    used_at: datetime | None = None
    max_uses: int = 1
    use_count: int = 0
    amount: Decimal = Decimal("0")
    currency: str = "USD"
    description: str | None = None
    metadata: dict[str, Any] = field(default_factory=dict)


# Webhook delivery models
@dataclass
class WebhookDelivery:
    """Webhook delivery record with retry support."""
    delivery_id: str = field(default_factory=lambda: str(uuid.uuid4()))
    webhook_id: str = ""
    endpoint_url: str = ""
    event_type: str = ""
    payload: dict[str, Any] = field(default_factory=dict)
    status: WebhookDeliveryStatus = WebhookDeliveryStatus.PENDING
    attempt_count: int = 0
    max_attempts: int = 5
    created_at: datetime = field(default_factory=datetime.utcnow)
    last_attempt_at: datetime | None = None
    next_retry_at: datetime | None = None
    delivered_at: datetime | None = None
    response_status_code: int | None = None
    response_body: str | None = None
    error_message: str | None = None
    headers: dict[str, str] = field(default_factory=dict)


@dataclass
class WebhookEndpoint:
    """Registered webhook endpoint."""
    endpoint_id: str = field(default_factory=lambda: str(uuid.uuid4()))
    url: str = ""
    secret: str = ""
    events: list[str] = field(default_factory=list)  # Event types to receive
    enabled: bool = True
    created_at: datetime = field(default_factory=datetime.utcnow)
    metadata: dict[str, Any] = field(default_factory=dict)
    failure_count: int = 0
    last_success_at: datetime | None = None
    last_failure_at: datetime | None = None


# Checkout customization models
@dataclass
class CheckoutCustomization:
    """Checkout UI and behavior customization."""
    # Branding
    logo_url: str | None = None
    primary_color: str | None = None
    background_color: str | None = None
    button_color: str | None = None
    font_family: str | None = None
    # Display
    display_name: str | None = None
    display_description: str | None = None
    terms_url: str | None = None
    privacy_url: str | None = None
    support_email: str | None = None
    support_phone: str | None = None
    # Behavior
    collect_billing_address: bool = False
    collect_shipping_address: bool = False
    collect_phone_number: bool = False
    require_terms_acceptance: bool = False
    allow_promotion_codes: bool = False
    # Locale
    locale: str = "en"
    # Custom fields
    custom_fields: list[dict[str, Any]] = field(default_factory=list)
    # Additional metadata
    metadata: dict[str, Any] = field(default_factory=dict)


# Fraud detection models
@dataclass
class FraudSignal:
    """Individual fraud detection signal."""
    signal_type: str = ""  # e.g., "velocity", "geolocation", "device"
    signal_value: Any = None
    risk_score: float = 0.0  # Supports both 0..1 and 0..100 conventions
    confidence: float = 1.0  # 0.0 to 1.0
    details: dict[str, Any] = field(default_factory=dict)
    # Backward-compatible fields
    name: str | None = None
    provider: str | None = None
    description: str | None = None
    metadata: dict[str, Any] = field(default_factory=dict)

    def __post_init__(self) -> None:
        # Keep old/new names interoperable.
        if self.name and not self.signal_type:
            self.signal_type = self.name
        if not self.name and self.signal_type:
            self.name = self.signal_type

        # Keep old/new metadata containers interoperable.
        if self.metadata and not self.details:
            self.details = dict(self.metadata)
        elif self.details and not self.metadata:
            self.metadata = dict(self.details)


@dataclass
class FraudCheckResult:
    """Result of fraud detection check."""
    check_id: str = field(default_factory=lambda: str(uuid.uuid4()))
    checkout_id: str = ""
    decision: FraudDecision = FraudDecision.APPROVE
    risk_level: FraudRiskLevel = FraudRiskLevel.LOW
    risk_score: float = 0.0  # Overall score 0.0 to 100.0
    signals: list[FraudSignal] = field(default_factory=list)
    rules_triggered: list[str] = field(default_factory=list)
    provider: str = "internal"  # fraud detection provider
    provider_reference: str | None = None
    checked_at: datetime = field(default_factory=datetime.utcnow)
    review_reason: str | None = None
    manual_review_required: bool = False
    metadata: dict[str, Any] = field(default_factory=dict)


@dataclass
class FraudRule:
    """Fraud detection rule configuration."""
    rule_id: str = field(default_factory=lambda: str(uuid.uuid4()))
    name: str = ""
    description: str | None = None
    enabled: bool = True
    rule_type: str = "threshold"  # threshold, velocity, pattern, ml
    conditions: dict[str, Any] = field(default_factory=dict)
    action: FraudDecision = FraudDecision.REVIEW
    priority: int = 100
    created_at: datetime = field(default_factory=datetime.utcnow)
    updated_at: datetime = field(default_factory=datetime.utcnow)
    metadata: dict[str, Any] = field(default_factory=dict)
