"""
Pydantic schemas for the Authorization detail, expire, and payment-update endpoints.
"""
from pydantic import BaseModel, ConfigDict
from typing import Optional, List, Dict, Any
from datetime import datetime


class AuthorizationHppSessionSchema(BaseModel):
    ip_address: Optional[str] = None
    user_agent: Optional[str] = None
    geo_city: Optional[str] = None
    geo_region: Optional[str] = None
    geo_country: Optional[str] = None
    geo_isp: Optional[str] = None
    initiated_at: Optional[datetime] = None
    submitted_at: Optional[datetime] = None

    model_config = ConfigDict(from_attributes=True)


class AuthorizationPaymentRequestSchema(BaseModel):
    id: int
    payment_request_id: Optional[str] = None
    payment_request_literal: Optional[str] = None
    amount: Optional[float] = None
    payment_frequency: Optional[str] = None
    currency: Optional[str] = None
    status: Optional[int] = None
    title: Optional[str] = None
    description: Optional[str] = None
    due_date: Optional[datetime] = None

    model_config = ConfigDict(from_attributes=True)


class AuthorizationCustomerSchema(BaseModel):
    id: int
    customer_id: Optional[str] = None
    account_literal: Optional[str] = None
    first_name: Optional[str] = None
    last_name: Optional[str] = None
    email: Optional[str] = None
    phone: Optional[str] = None
    business_legal_name: Optional[str] = None

    model_config = ConfigDict(from_attributes=True)


class AuthorizationPayerSchema(BaseModel):
    id: int
    contact_id: Optional[str] = None
    first_name: Optional[str] = None
    last_name: Optional[str] = None
    email: Optional[str] = None
    phone: Optional[str] = None

    model_config = ConfigDict(from_attributes=True)


class AuthorizationProofSchema(BaseModel):
    """Proof-of-authorization details, content varies by authorization_type."""
    authorization_type: str
    # CHECKBOX: signing_name and/or authorization_literal
    signing_name: Optional[str] = None
    authorization_literal: Optional[str] = None
    # SIGNATURE: presigned URL to the signature image
    signature_url: Optional[str] = None
    # SMS_OTP: decrypted OTP value (admin/merchant-only; handle with care)
    authorization_value: Optional[str] = None
    # SMS_OTP: last 4 digits of the verified phone number
    verified_phone_last4: Optional[str] = None
    # Common: IP address captured at time of authorization
    ip_address: Optional[str] = None


class AuthorizationHistoryEvent(BaseModel):
    event: str
    occurred_at: datetime
    metadata: Optional[Dict[str, Any]] = None


class AuthorizationDetailResponse(BaseModel):
    id: int
    authorization_id: Optional[str] = None
    authorization_literal: Optional[str] = None
    authorization_type: str
    authorization_date: Optional[datetime] = None
    status: Optional[str] = None
    is_verified: bool
    created_at: datetime
    updated_at: Optional[datetime] = None
    expired_at: Optional[datetime] = None
    auth_metadata: Optional[Dict[str, Any]] = None

    payment_request: Optional[AuthorizationPaymentRequestSchema] = None
    customer: Optional[AuthorizationCustomerSchema] = None
    payer: Optional[AuthorizationPayerSchema] = None
    hpp_session: Optional[AuthorizationHppSessionSchema] = None
    proof: Optional[AuthorizationProofSchema] = None
    history: List[AuthorizationHistoryEvent] = []


class ExpireAuthorizationResponse(BaseModel):
    authorization_id: str
    status: str
    expired_at: datetime
    message: str


_ALLOWED_CHANNELS = {"email", "sms"}


class RequestPaymentUpdateRequest(BaseModel):
    contact_ids: List[int]
    channels: List[str]
    note: Optional[str] = None

    def model_post_init(self, __context: Any) -> None:
        invalid = [c for c in self.channels if c not in _ALLOWED_CHANNELS]
        if invalid:
            raise ValueError(
                f"Invalid notification channel(s): {invalid}. "
                f"Allowed values are: {sorted(_ALLOWED_CHANNELS)}."
            )
        if self.note and len(self.note) > 500:
            raise ValueError("note must not exceed 500 characters.")
        if len(self.contact_ids) > 50:
            raise ValueError("contact_ids must not exceed 50 entries.")


class RequestPaymentUpdateResponse(BaseModel):
    authorization_id: str
    # update_token is intentionally NOT included in the API response.
    # The token is a sensitive payment-update credential that is dispatched
    # directly to the customer via the notification channel (email/SMS).
    # Returning it here would expose it to the merchant portal session,
    # browser history, and any response-logging middleware.
    notifications_dispatched: int
    message: str


class AssociatedInvoiceSchema(BaseModel):
    id: int
    invoice_id: Optional[str] = None
    invoice_literal: Optional[str] = None
    amount: Optional[float] = None
    status: Optional[int] = None
    status_text: Optional[str] = None
    created_at: Optional[datetime] = None
    due_date: Optional[datetime] = None


class AssociatedTransactionSchema(BaseModel):
    id: int
    txn_id: Optional[str] = None
    txn_literal: Optional[str] = None
    txn_amount: Optional[float] = None
    txn_status: Optional[int] = None
    status_text: Optional[str] = None
    ocurred_at: Optional[datetime] = None
    payment_type: Optional[str] = None
    authorization_type: Optional[str] = None


class AuthorizationAssociationsResponse(BaseModel):
    invoices: List[AssociatedInvoiceSchema] = []
    transactions: List[AssociatedTransactionSchema] = []


class AuthorizationInvoicesResponse(BaseModel):
    invoices: List[AssociatedInvoiceSchema] = []
    total: int = 0


class AuthorizationTransactionsResponse(BaseModel):
    transactions: List[AssociatedTransactionSchema] = []
    total: int = 0


class AuthorizationProofResponse(BaseModel):
    proof: Optional[AuthorizationProofSchema] = None


class AuthorizationHistoryResponse(BaseModel):
    history: List[AuthorizationHistoryEvent] = []
