"""
CartSession ORM model.

Represents a single shopping-cart checkout session created by the JS SDK
on behalf of a merchant. Each session holds all totals in integer cents
to avoid floating-point rounding errors.
"""
from __future__ import annotations

from datetime import datetime
from typing import Optional, List, TYPE_CHECKING

from sqlalchemy import (
    Boolean, CheckConstraint, DateTime, ForeignKey, Integer, JSON, String, Text
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.sql import func

from src.apps.base.models.base import Base

if TYPE_CHECKING:
    from src.apps.cart_plugin.models.merchant_widget_key import MerchantWidgetKey
    from src.apps.cart_plugin.models.cart_session_item import CartSessionItem


class CartSession(Base):
    """A single checkout session created via the Shopping Cart Plugin SDK."""

    __tablename__ = "cart_sessions"
    __table_args__ = (
        CheckConstraint(
            "status IN ('PENDING','PAID','EXPIRED','CANCELLED')",
            name="ck_cart_sessions_status",
        ),
    )

    id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True, autoincrement=True)

    # Opaque token used in all public URLs — never expose the integer PK.
    token: Mapped[str] = mapped_column(String(64), unique=True, index=True, nullable=False)

    merchant_id: Mapped[int] = mapped_column(Integer, ForeignKey("merchants.id"), nullable=False, index=True)

    widget_key_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("merchant_widget_keys.id"), nullable=False, index=True
    )

    # ── Status ─────────────────────────────────────────────────────────────────
    status: Mapped[str] = mapped_column(String(20), nullable=False, default="PENDING", index=True)

    # ── Config ─────────────────────────────────────────────────────────────────
    currency: Mapped[str] = mapped_column(String(10), nullable=False, default="USD")
    origin: Mapped[Optional[str]] = mapped_column(String(500), nullable=True)
    return_url: Mapped[Optional[str]] = mapped_column(String(500), nullable=True)
    checkout_mode: Mapped[Optional[str]] = mapped_column(String(20), nullable=True)

    # ── Payer info ─────────────────────────────────────────────────────────────
    customer_name: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
    customer_email: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
    payer_user_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("users.id"), nullable=True)

    billing_info: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)

    # ── Financials (all in integer cents) ──────────────────────────────────────
    discount_code: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
    discount_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
    tip_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
    tax_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
    subtotal: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
    total: Mapped[int] = mapped_column(Integer, nullable=False, default=0)

    # ── Result ─────────────────────────────────────────────────────────────────
    # NOTE: transaction_id is intentionally kept for a future migration when the
    # Transactions model supports nullable payment_request_id / customer_id FKs.
    # It is always NULL in the current implementation — use provider_txn_ref instead.
    transaction_id: Mapped[Optional[int]] = mapped_column(
        Integer, ForeignKey("transactions.id"), nullable=True
    )
    # Provider's own transaction reference (e.g. Payrix TXN ID).
    provider_txn_ref: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)

    # ── Metadata / webhook ─────────────────────────────────────────────────────
    metadata_: Mapped[Optional[dict]] = mapped_column("metadata", JSON, nullable=True)

    last_webhook_attempt_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
    last_webhook_status: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)

    # ── Idempotency ────────────────────────────────────────────────────────────
    # Partial unique index on (widget_key_id, idempotency_key) is defined
    # in the Alembic migration — not here, to avoid Alembic autogenerate conflicts.
    idempotency_key: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)

    # ── Timestamps ─────────────────────────────────────────────────────────────
    expires_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
    created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
    updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True, onupdate=func.now())

    # ── Relationships ──────────────────────────────────────────────────────────
    widget_key: Mapped["MerchantWidgetKey"] = relationship(
        "MerchantWidgetKey", back_populates="sessions"
    )

    items: Mapped[List["CartSessionItem"]] = relationship(
        "CartSessionItem",
        back_populates="session",
        cascade="all, delete-orphan",
        lazy="selectin",
    )
