"""
HPPRetryToken model — customer-facing retry links for failed installment
payments (HWHPP-206).

When a scheduled Celery payment task fails, the notification listener
creates one HPPRetryToken per failed Transaction.  The customer receives
an email with a link containing the token; they can open it, select a
different payment method, and re-attempt the installment.

Token tables intentionally omit deleted_at (immutable audit records).
"""

from datetime import datetime
from typing import Optional, TYPE_CHECKING

from sqlalchemy import ForeignKey, Index, Integer, String, DateTime
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.transactions.models.transactions import Transactions
    from src.apps.payment_requests.models.payment_request import PaymentRequest


class HPPRetryToken(Base):
    """
    HPPRetryToken Model: ORM class for the hpp_retry_tokens table.

    One row per customer retry link.  Max 3 active retry tokens per
    failed transaction are allowed (enforced in services.py by counting
    rows per transaction_id where used_at IS NULL).
    """

    __tablename__ = "hpp_retry_tokens"

    __table_args__ = (
        # Look up a retry token by its URL-safe token string (primary
        # query when customer opens the retry link).
        Index("ix_hpp_retry_tokens_token", "token", unique=True),
        # Find all retry tokens for a given failed transaction (used to
        # enforce the 3-attempt limit and to list retries in admin).
        Index("ix_hpp_retry_tokens_transaction_id", "transaction_id"),
    )

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

    # The specific failed transaction this retry token covers.
    transaction_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("transactions.id"), nullable=False
    )

    # Denormalised FK to payment_request for faster scope queries and
    # to allow the retry/initiate endpoint to return invoice context
    # without joining through transactions.
    payment_request_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("payment_requests.id"), nullable=False
    )

    # URL-safe cryptographically random token (secrets.token_urlsafe(32)).
    # Has a unique constraint via the index above.
    token: Mapped[str] = mapped_column(String(255), nullable=False)

    # Token expiry — set to now() + 72 hours at creation time (service layer).
    expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)

    # Stamped when the customer successfully completes a retry payment.
    used_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)

    # Number of failed retry attempts made with this specific token.
    # (Distinct from the 3-token limit, which counts rows.)
    attempt_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)

    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), nullable=False
    )

    # Relationships
    transaction: Mapped["Transactions"] = relationship(
        "Transactions", back_populates="retry_tokens"
    )
    payment_request: Mapped["PaymentRequest"] = relationship(
        "PaymentRequest", back_populates="retry_tokens"
    )
