"""
Subscription ORM model.

A Subscription is the long-lived record that tracks the recurring billing
lifecycle for a single PaymentRequest configured with
payment_frequency = 'recurring'.

One PaymentRequest → one Subscription (unique FK).
One Subscription → many Invoices (one per billing cycle).
One Subscription → many SubscriptionActivity records (audit trail).
"""

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

from sqlalchemy import DateTime, Float, ForeignKey, Integer, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.sql import func

from src.apps.base.models.base import Base
from src.apps.subscriptions.enums import SubscriptionStatus

if TYPE_CHECKING:
    from src.apps.merchants.models.merchant import Merchant
    from src.apps.customers.models.customer import Customer
    from src.apps.payment_requests.models.payment_request import PaymentRequest
    from src.apps.invoices.models.invoice import Invoice
    from src.apps.subscriptions.models.subscription_activity import SubscriptionActivity


class Subscription(Base):
    """
    Persistent record that tracks a recurring billing contract.

    subscription_id  — opaque public identifier (``sub_<random>``).
    subscription_literal — sequential human-readable label (``SUB000001``).
    status           — SubscriptionStatus integer enum (indexed for fast
                       scheduler queries).

    Running totals (total_billed / total_paid / invoices_generated /
    invoices_paid) are maintained by the invoice listener so the subscription
    detail view never needs an expensive aggregation query.

    Dunning columns are populated by the charge-retry Celery task:
        dunning_retry_count     — how many retries have been attempted
        dunning_last_retry_at   — when the last retry ran
        dunning_next_retry_at   — when the next retry is scheduled
        dunning_exhausted_at    — set when max retries are exceeded
    """

    __tablename__ = "subscriptions"

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

    # ── Public identifiers ────────────────────────────────────────────────────

    subscription_id: Mapped[str] = mapped_column(
        String(64),
        unique=True,
        index=True,
        nullable=False,
        doc="Opaque public identifier, e.g. sub_abc123…",
    )
    subscription_literal: Mapped[str] = mapped_column(
        String(20),
        unique=True,
        nullable=False,
        doc="Sequential human-readable label, e.g. SUB000001",
    )
    name: Mapped[Optional[str]] = mapped_column(
        String(255),
        nullable=True,
        doc="Optional merchant-supplied display name for this subscription.",
    )

    # ── Status ────────────────────────────────────────────────────────────────

    status: Mapped[int] = mapped_column(
        Integer,
        default=SubscriptionStatus.INITIALIZING,
        index=True,
        nullable=False,
    )

    # ── Foreign keys (multi-tenancy: flat merchant_id) ────────────────────────

    merchant_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("merchants.id"), nullable=False, index=True
    )
    customer_id: Mapped[Optional[int]] = mapped_column(
        Integer, ForeignKey("customers.id"), nullable=True, index=True
    )
    payment_request_id: Mapped[int] = mapped_column(
        Integer,
        ForeignKey("payment_requests.id"),
        unique=True,
        nullable=False,
        doc="One-to-one: each RecurringPaymentRequest produces exactly one Subscription.",
    )

    # ── Running totals ────────────────────────────────────────────────────────

    total_billed: Mapped[float] = mapped_column(
        Float,
        default=0.0,
        nullable=False,
        doc="Cumulative amount billed across all generated invoices.",
    )
    total_paid: Mapped[float] = mapped_column(
        Float,
        default=0.0,
        nullable=False,
        doc="Cumulative amount successfully collected.",
    )
    invoices_generated: Mapped[int] = mapped_column(
        Integer,
        default=0,
        nullable=False,
        doc="Count of invoices produced by this subscription.",
    )
    invoices_paid: Mapped[int] = mapped_column(
        Integer,
        default=0,
        nullable=False,
        doc="Count of invoices that reached PAID status.",
    )

    # ── Scheduling ────────────────────────────────────────────────────────────

    next_billing_date: Mapped[Optional[datetime]] = mapped_column(
        DateTime(timezone=True),
        nullable=True,
        doc="When the next invoice should be generated and charged.",
    )
    past_due_since: Mapped[Optional[datetime]] = mapped_column(
        DateTime(timezone=True),
        nullable=True,
        doc="Stamped when a charge fails and status moves to PAST_DUE.",
    )

    # ── Dunning state ─────────────────────────────────────────────────────────

    dunning_retry_count: Mapped[int] = mapped_column(
        Integer,
        default=0,
        nullable=False,
        doc="Number of retry attempts made since last failure.",
    )
    dunning_last_retry_at: Mapped[Optional[datetime]] = mapped_column(
        DateTime(timezone=True),
        nullable=True,
        doc="Timestamp of the most recent dunning retry attempt.",
    )
    dunning_next_retry_at: Mapped[Optional[datetime]] = mapped_column(
        DateTime(timezone=True),
        nullable=True,
        doc="Scheduled timestamp for the next dunning retry.",
    )
    dunning_exhausted_at: Mapped[Optional[datetime]] = mapped_column(
        DateTime(timezone=True),
        nullable=True,
        doc="Stamped when max retries are exceeded and status moves to DUNNING_EXHAUSTED.",
    )

    # ── Timestamps ────────────────────────────────────────────────────────────

    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True),
        server_default=func.now(),
        nullable=False,
    )
    updated_at: Mapped[Optional[datetime]] = mapped_column(
        DateTime(timezone=True),
        nullable=True,
        onupdate=func.now(),
    )
    deleted_at: Mapped[Optional[datetime]] = mapped_column(
        DateTime(timezone=True),
        nullable=True,
        doc="Soft-delete timestamp. Queries must filter WHERE deleted_at IS NULL.",
    )

    # ── Relationships ─────────────────────────────────────────────────────────

    merchant: Mapped["Merchant"] = relationship("Merchant")

    customer: Mapped[Optional["Customer"]] = relationship("Customer")

    payment_request: Mapped["PaymentRequest"] = relationship(
        "PaymentRequest",
        back_populates="subscription",
        uselist=False,
    )

    invoices: Mapped[List["Invoice"]] = relationship(
        "Invoice",
        back_populates="subscription",
        order_by="Invoice.created_at.desc()",
    )

    activities: Mapped[List["SubscriptionActivity"]] = relationship(
        "SubscriptionActivity",
        back_populates="subscription",
        order_by="SubscriptionActivity.created_at.desc()",
    )
