"""
Pydantic schemas for the HPP (Hosted Payment Page) module.

Covers all request/response shapes for:
  - /payment-requests/hpp/initiate
  - /payment-requests/hpp/submit
  - /payment-requests/hpp/retry/initiate
  - /payment-requests/hpp/retry/submit
  - /hpp/otp/send
  - /hpp/otp/verify
  - /hpp/customer/register
  - /hpp/customer/login
  - /hpp/customer/verify-email
  - Merchant link management endpoints
"""

import re
from datetime import datetime
from typing import Any, Dict, List, Optional
from pydantic import BaseModel, EmailStr, Field, field_validator, model_validator


# ─── HPP Initiate ─────────────────────────────────────────────────────────────

class MerchantBrandingSchema(BaseModel):
    """Merchant branding data returned in the initiate payload."""

    merchant_name: str
    primary_color: Optional[str] = None
    secondary_color: Optional[str] = None
    accent_color: Optional[str] = None
    logo_url: Optional[str] = None
    support_email: Optional[str] = None
    support_phone: Optional[str] = None
    website_url: Optional[str] = None
    show_support_email: bool = True
    show_support_phone: bool = True
    show_website_url: bool = True


class SavedPaymentMethodSchema(BaseModel):
    """A saved payment method (masked) for the customer to select."""

    id: int
    card_last4: Optional[str] = None
    card_brand: Optional[str] = None
    exp_month: Optional[int] = None
    exp_year: Optional[int] = None
    payment_type: Optional[str] = None  # "card" | "bank"
    is_default: bool = False


class LineItemSchema(BaseModel):
    """A single line-item on the payment request."""

    description: str
    quantity: float = 1.0
    unit_price: float
    total: float
    product_id: Optional[int] = None


class HppInitiateResponseSchema(BaseModel):
    """Full payload returned by GET /payment-requests/hpp/initiate."""

    payment_request_id: int
    payment_request_literal: Optional[str] = None
    amount: float
    currency: str
    payment_frequency: Optional[str] = None
    authorization_type: Optional[str] = None
    message: Optional[str] = None
    title: Optional[str] = None
    description: Optional[str] = None
    terms: Optional[str] = None
    due_date: Optional[datetime] = None
    billing_date: Optional[datetime] = None
    allow_tip: bool = False
    tip_btn_1: str = "10"
    tip_btn_2: str = "15"
    tip_btn_3: str = "20"
    tip_btn_4: str = "25"
    tip_btn_type: str = "percentage"
    # Invoice display settings (from merchant invoice_settings)
    show_item_description: bool = True
    show_customer_name: bool = True
    show_transaction_time: bool = True
    show_invoice_number: bool = True
    # Receipt display settings (from merchant receipt_settings)
    receipt_show_item_description: bool = True
    receipt_show_customer_name: bool = True
    receipt_show_transaction_time: bool = True
    receipt_show_receipt_number: bool = True
    require_billing_address: bool = False
    require_shipping_address: bool = False
    require_cvv: bool = True
    require_sms_authorization: bool = False
    require_signature_authorization: bool = False
    save_payment_method: bool = False
    line_items: List[LineItemSchema] = []
    branding: MerchantBrandingSchema
    customer_name: Optional[str] = None
    customer_email: Optional[str] = None
    customer_phone: Optional[str] = None
    saved_payment_methods: List[SavedPaymentMethodSchema] = []
    hpp_session_id: int
    customer_has_account: bool = False
    provider_config: Optional[dict] = None
    # Payer display settings
    display_payer_ip: bool = False
    display_payer_location: bool = False
    payer_ip_address: Optional[str] = None
    payer_location: Optional[str] = None
    # Whether an invoice PDF is downloadable for this payment request
    has_invoice: bool = False


# ─── HPP Submit ───────────────────────────────────────────────────────────────

class HppSubmitSchema(BaseModel):
    """Request body for POST /payment-requests/hpp/submit."""

    token: str = Field(..., description="HPP payment link token")

    # Payment method — exactly one of saved_payment_method_id or hosted_form_token
    saved_payment_method_id: Optional[int] = Field(
        None, description="ID of an existing saved payment method"
    )
    hosted_form_token: Optional[str] = Field(
        None, description="Tokenized card/bank data from the provider's hosted iframe"
    )

    # Tip
    tip_amount: float = Field(0.0, ge=0, description="Customer-selected tip amount")

    # authorization_type: intentionally not used server-side — server derives this from
    # payment_request record (SEC-001). Kept here for backward-compatible client payloads only.
    authorization_type: Optional[str] = Field(
        None, description="IGNORED SERVER-SIDE. Server derives auth type from the payment request record."
    )
    agree_to_terms: bool = Field(
        False, description="True when customer ticks the terms checkbox"
    )
    signature_data_url: Optional[str] = Field(
        None, description="Base64 PNG data URL of drawn signature (SIGNATURE auth only)"
    )
    # otp_verified is intentionally ignored server-side (B-1).
    # The server re-validates OTP verification status from the DB directly.
    # This field is kept in the schema for backwards-compatible client payloads
    # but is never trusted — do not read it in service logic.
    otp_verified: bool = Field(
        False,
        description=(
            "IGNORED SERVER-SIDE. Kept for client compatibility only. "
            "The server re-validates OTP status from the DB."
        ),
    )

    # Billing / shipping addresses (optional, only when required by PR config)
    billing_address: Optional[Dict[str, Any]] = None
    shipping_address: Optional[Dict[str, Any]] = None

    # Customer account opt-in
    save_payment_method: bool = False

    @model_validator(mode="after")
    def validate_pci_guard(self) -> "HppSubmitSchema":
        """
        B-3 PCI guard: in non-stub (production) mode, raw card fields must never
        reach this endpoint — the provider's hosted iframe tokenises them first.
        Require hosted_form_token when PAYMENT_PROVIDER_STUB is False (unless a
        saved payment method is being used instead).
        """
        from src.core.config import settings

        if not settings.PAYMENT_PROVIDER_STUB:
            # A saved payment method is acceptable without a hosted_form_token.
            if not self.saved_payment_method_id and not self.hosted_form_token:
                raise ValueError(
                    "hosted_form_token is required when not using a saved payment method. "
                    "Raw card data must be tokenised via the provider's hosted iframe."
                )

        return self


class HppSubmitResponseSchema(BaseModel):
    """Response returned after a successful HPP payment submission."""

    transaction_id: int
    txn_id: str
    txn_literal: Optional[str] = None
    txn_amount: float
    txn_status: int
    status_text: str
    payment_request_id: int
    is_scheduled: bool = False
    receipt_url: Optional[str] = None
    card_last4: Optional[str] = None
    message: str = "Payment submitted successfully"


# ─── HPP Retry ────────────────────────────────────────────────────────────────

class HppRetryInitiateResponseSchema(BaseModel):
    """Payload returned by GET /payment-requests/hpp/retry/initiate."""

    retry_token_id: int
    payment_request_id: int
    transaction_id: int
    amount: float
    currency: str
    merchant_name: str
    branding: MerchantBrandingSchema
    saved_payment_methods: List[SavedPaymentMethodSchema] = []


class HppRetrySubmitSchema(BaseModel):
    """Request body for POST /payment-requests/hpp/retry/submit."""

    token: str = Field(..., description="Retry token")
    saved_payment_method_id: Optional[int] = None
    hosted_form_token: Optional[str] = None

    @model_validator(mode="after")
    def validate_pci_guard(self) -> "HppRetrySubmitSchema":
        """
        SEC-004 PCI guard: in non-stub (production) mode, raw card fields must never
        reach this endpoint — the provider's hosted iframe tokenises them first.
        Require hosted_form_token when PAYMENT_PROVIDER_STUB is False (unless a
        saved payment method is being used instead).
        """
        from src.core.config import settings

        if not settings.PAYMENT_PROVIDER_STUB:
            if not self.saved_payment_method_id and not self.hosted_form_token:
                raise ValueError(
                    "hosted_form_token is required when not using a saved payment method. "
                    "Raw card data must be tokenised via the provider's hosted iframe."
                )

        return self


class HppRetrySubmitResponseSchema(BaseModel):
    """Response after a successful retry payment."""

    transaction_id: int
    txn_id: str
    txn_amount: float
    status_text: str
    message: str = "Retry payment processed successfully"


# ─── OTP ──────────────────────────────────────────────────────────────────────

class OtpSendSchema(BaseModel):
    """Request body for POST /hpp/otp/send."""

    token: str = Field(..., description="HPP payment link token")


class OtpSendResponseSchema(BaseModel):
    """Response after sending an OTP."""

    phone_last4: str
    send_count: int
    expires_in_seconds: int = 600
    message: str = "OTP sent successfully"


class OtpVerifySchema(BaseModel):
    """Request body for POST /hpp/otp/verify."""

    token: str = Field(..., description="HPP payment link token")
    otp: str = Field(..., min_length=6, max_length=6, description="6-digit OTP")


class OtpVerifyResponseSchema(BaseModel):
    """Response after OTP verification."""

    verified: bool
    attempt_count: int
    message: str = "OTP verified successfully"


# ─── Customer account ─────────────────────────────────────────────────────────

# Password complexity requirements (C-7)
_PASSWORD_UPPERCASE_RE = re.compile(r"[A-Z]")
_PASSWORD_DIGIT_RE = re.compile(r"\d")
_PASSWORD_SPECIAL_RE = re.compile(r"[!@#$%^&*()]")


class CustomerRegisterSchema(BaseModel):
    """Request body for POST /hpp/customer/register."""

    token: str = Field(..., description="HPP payment link token")
    email: EmailStr
    password: str = Field(..., min_length=8)
    confirm_password: str

    @field_validator("password")
    @classmethod
    def validate_password_complexity(cls, v: str) -> str:
        """
        C-7: Enforce server-side password complexity rules:
          - At least 1 uppercase letter
          - At least 1 digit
          - At least 1 special character from !@#$%^&*()
        """
        errors = []
        if not _PASSWORD_UPPERCASE_RE.search(v):
            errors.append("at least one uppercase letter")
        if not _PASSWORD_DIGIT_RE.search(v):
            errors.append("at least one digit")
        if not _PASSWORD_SPECIAL_RE.search(v):
            errors.append("at least one special character (!@#$%^&*())")
        if errors:
            raise ValueError(
                "Password must contain " + ", ".join(errors) + "."
            )
        return v

    @field_validator("confirm_password")
    @classmethod
    def passwords_match(cls, v: str, info: Any) -> str:
        if "password" in info.data and v != info.data["password"]:
            raise ValueError("Passwords do not match")
        return v


class CustomerRegisterResponseSchema(BaseModel):
    """Response after customer registration."""

    user_id: int
    email: str
    is_verified: bool = False
    message: str = "Account created. Please verify your email."


class CustomerLoginSchema(BaseModel):
    """Request body for POST /hpp/customer/login."""

    token: str = Field(..., description="HPP payment link token")
    email: EmailStr
    password: str


class CustomerLoginResponseSchema(BaseModel):
    """Response after customer login."""

    access_token: str
    token_type: str = "bearer"
    expires_in: int = 1800  # 30 minutes for HPP customer sessions
    message: str = "Login successful"


# ─── Merchant link management ─────────────────────────────────────────────────

class PaymentLinkSchema(BaseModel):
    """A single payment link record returned to the merchant."""

    id: int
    masked_token: str
    status: str
    click_count: int
    created_at: Optional[datetime] = None
    used_at: Optional[datetime] = None
    revoked_at: Optional[datetime] = None
    last_clicked_at: Optional[datetime] = None
    end_date: Optional[datetime] = None


class PaymentLinksListResponseSchema(BaseModel):
    """Response for GET /payment-requests/{id}/links."""

    links: List[PaymentLinkSchema]
    total: int


class ResendLinkResponseSchema(BaseModel):
    """Response for POST /payment-requests/{id}/hpp/resend."""

    link_id: int
    masked_token: str
    token: str  # Raw token returned to the authenticated merchant for HPP URL construction
    message: str = "Notification re-sent successfully"


class RevokeLinkResponseSchema(BaseModel):
    """Response for DELETE /payment-requests/{id}/links/{link_id}."""

    link_id: int
    status: str = "REVOKED"
    message: str = "Link revoked successfully"


# ─── Transaction retry (merchant-triggered) ──────────────────────────────────

class TransactionRetryResponseSchema(BaseModel):
    """Response for POST /transactions/{id}/retry."""

    retry_token_id: int
    token: str
    expires_at: datetime
    message: str = "Retry link created and notification sent"
