"""ORM models for the licenses + gumroad_events tables.

Schema mirrors ``docs/LICENSE-SERVER.md``, generalized so any
``source`` can populate it. The ``(source, source_order_id)``
composite uniqueness key gives idempotent webhook retries — a
storefront firing the same sale twice maps to the same row.
"""

from __future__ import annotations

from datetime import datetime
from typing import Optional

from sqlalchemy import (
    JSON,
    BigInteger,
    DateTime,
    Index,
    Integer,
    Numeric,
    String,
    UniqueConstraint,
    func,
    text,
)
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column

# JSONB on Postgres (indexable, queryable), plain JSON elsewhere
# (SQLite for tests). Same Python interface either way.
_JSON_TYPE = JSON().with_variant(JSONB(), "postgresql")

# SQLite only auto-increments INTEGER PRIMARY KEY (not BIGINT).
# Postgres can autoincrement either, so the variant keeps the
# production migration on BigInteger while tests use Integer.
_PK_TYPE = BigInteger().with_variant(Integer(), "sqlite")

from app.db import Base


class License(Base):
    __tablename__ = "licenses"

    license_key: Mapped[str] = mapped_column(String, primary_key=True)
    name: Mapped[str] = mapped_column(String, nullable=False)
    email: Mapped[str] = mapped_column(String, nullable=False)
    tier: Mapped[str] = mapped_column(String, nullable=False)
    issued_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
    expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
    blob: Mapped[str] = mapped_column(String, nullable=False)

    source: Mapped[str] = mapped_column(String, nullable=False)
    source_order_id: Mapped[Optional[str]] = mapped_column(String, nullable=True)
    promotion: Mapped[Optional[str]] = mapped_column(String, nullable=True)
    amount_paid: Mapped[Optional[float]] = mapped_column(Numeric(10, 2), nullable=True)
    currency: Mapped[Optional[str]] = mapped_column(String(3), nullable=True, server_default=text("'USD'"))

    revoked_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
    notes: Mapped[Optional[str]] = mapped_column(String, nullable=True)

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

    __table_args__ = (
        UniqueConstraint("source", "source_order_id", name="uq_licenses_source_order"),
        Index("ix_licenses_email_lower", func.lower(text("email"))),
        Index("ix_licenses_expires_active", "expires_at", postgresql_where=text("revoked_at IS NULL")),
    )


class GumroadEvent(Base):
    """Append-only audit log of every webhook delivery.

    Stored regardless of processing outcome so we can replay failed
    events, investigate disputes, and reconstruct the customer
    record if the ``licenses`` table is ever corrupted.
    """

    __tablename__ = "gumroad_events"

    id: Mapped[int] = mapped_column(_PK_TYPE, primary_key=True, autoincrement=True)
    received_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
    event_type: Mapped[str] = mapped_column(String, nullable=False)
    order_id: Mapped[Optional[str]] = mapped_column(String, nullable=True)
    raw_payload: Mapped[dict] = mapped_column(_JSON_TYPE, nullable=False)
    processed: Mapped[bool] = mapped_column(server_default=text("false"), nullable=False)
    error: Mapped[Optional[str]] = mapped_column(String, nullable=True)

    __table_args__ = (
        Index("ix_gumroad_events_order_id", "order_id"),
        Index("ix_gumroad_events_unprocessed", "received_at", postgresql_where=text("processed = false")),
    )
