"""MPS-20 + MPS-21: pricing_mode, auction and offer tables

Revision ID: f0213a54cf7b
Revises: 1a2b3c4d5e6f
Create Date: 2026-05-09 18:11:18.572145

Foundation for auction-house mode (MPS-20) and make-an-offer mode (MPS-21):
- Adds pricing_mode + allow_offers to mps_product
- Adds 7 offer-* settings columns to mps_shop
- Creates mps_auction, mps_bid, mps_auction_watcher tables
- Creates mps_offer, mps_offer_event tables

Idempotent — make init-db creates tables from models, so this migration
must skip create_table / add_column when the target already exists.
"""
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils


revision = 'f0213a54cf7b'
down_revision = '1a2b3c4d5e6f'
branch_labels = None
depends_on = None


def _table_exists(name):
    conn = op.get_bind()
    result = conn.execute(
        sa.text("SELECT name FROM sqlite_master WHERE type='table' AND name=:name"),
        {"name": name},
    )
    return result.fetchone() is not None


def _column_exists(table, column):
    conn = op.get_bind()
    result = conn.execute(sa.text(f"PRAGMA table_info({table})"))
    return any(row[1] == column for row in result.fetchall())


UUID = sqlalchemy_utils.types.uuid.UUIDType(binary=False)


def upgrade():
    # mps_product: pricing_mode + allow_offers
    if not _column_exists("mps_product", "pricing_mode"):
        op.add_column(
            "mps_product",
            sa.Column(
                "pricing_mode",
                sa.Integer(),
                server_default="0",
                nullable=False,
            ),
        )
    if not _column_exists("mps_product", "allow_offers"):
        op.add_column(
            "mps_product",
            sa.Column("allow_offers", sa.Boolean(), nullable=True),
        )

    # mps_shop: offer-* columns (7).
    shop_offer_columns = [
        ("offer_enabled", sa.Column("offer_enabled", sa.Boolean(), nullable=True)),
        ("offer_min_in_cents", sa.Column("offer_min_in_cents", sa.BigInteger(), nullable=True)),
        ("offer_auto_accept_threshold_pct", sa.Column("offer_auto_accept_threshold_pct", sa.BigInteger(), server_default="95", nullable=False)),
        ("offer_auto_decline_threshold_pct", sa.Column("offer_auto_decline_threshold_pct", sa.BigInteger(), server_default="50", nullable=False)),
        ("offer_expiration_hours", sa.Column("offer_expiration_hours", sa.BigInteger(), server_default="168", nullable=False)),
        ("offer_max_rounds", sa.Column("offer_max_rounds", sa.BigInteger(), server_default="3", nullable=False)),
        ("offer_min_buyer_account_age_hours", sa.Column("offer_min_buyer_account_age_hours", sa.BigInteger(), server_default="0", nullable=False)),
    ]
    for col_name, col in shop_offer_columns:
        if not _column_exists("mps_shop", col_name):
            op.add_column("mps_shop", col)

    # mps_auction
    if not _table_exists("mps_auction"):
        op.create_table(
            "mps_auction",
            sa.Column("id", UUID, nullable=False),
            sa.Column("product_id", UUID, nullable=False),
            sa.Column("shop_id", UUID, nullable=False),
            sa.Column("state", sa.Integer(), server_default="0", nullable=False),
            sa.Column("start_timestamp", sa.BigInteger(), nullable=True),
            sa.Column("end_timestamp", sa.BigInteger(), nullable=True),
            sa.Column("original_end_timestamp", sa.BigInteger(), nullable=True),
            sa.Column("start_price_in_cents", sa.BigInteger(), nullable=False),
            sa.Column("reserve_price_in_cents", sa.BigInteger(), nullable=True),
            sa.Column("buy_now_price_in_cents", sa.BigInteger(), nullable=True),
            sa.Column("bid_increment_in_cents", sa.BigInteger(), nullable=False),
            sa.Column("soft_close_seconds", sa.Integer(), nullable=False),
            sa.Column("winner_user_id", UUID, nullable=True),
            sa.Column("winning_bid_id", UUID, nullable=True),
            sa.Column("payment_deadline_timestamp", sa.BigInteger(), nullable=True),
            sa.Column("currency", sa.Unicode(length=3), nullable=False),
            sa.Column("created_timestamp", sa.BigInteger(), nullable=False),
            sa.Column("updated_timestamp", sa.BigInteger(), nullable=False),
            sa.ForeignKeyConstraint(["product_id"], ["mps_product.id"]),
            sa.ForeignKeyConstraint(["shop_id"], ["mps_shop.id"]),
            sa.ForeignKeyConstraint(["winner_user_id"], ["mps_user.id"]),
            sa.PrimaryKeyConstraint("id"),
        )
        op.create_index("ix_mps_auction_id", "mps_auction", ["id"])
        op.create_index("ix_mps_auction_product_id", "mps_auction", ["product_id"], unique=True)
        op.create_index("ix_mps_auction_shop_id", "mps_auction", ["shop_id"])

    # mps_bid
    if not _table_exists("mps_bid"):
        op.create_table(
            "mps_bid",
            sa.Column("id", UUID, nullable=False),
            sa.Column("auction_id", UUID, nullable=False),
            sa.Column("bidder_user_id", UUID, nullable=False),
            sa.Column("amount_in_cents", sa.BigInteger(), nullable=False),
            sa.Column("max_proxy_in_cents", sa.BigInteger(), nullable=True),
            sa.Column("created_timestamp", sa.BigInteger(), nullable=False),
            sa.Column("outbid_timestamp", sa.BigInteger(), nullable=True),
            sa.Column("is_winning", sa.Boolean(), server_default="0", nullable=False),
            sa.ForeignKeyConstraint(["auction_id"], ["mps_auction.id"]),
            sa.ForeignKeyConstraint(["bidder_user_id"], ["mps_user.id"]),
            sa.PrimaryKeyConstraint("id"),
        )
        op.create_index("ix_mps_bid_id", "mps_bid", ["id"])
        op.create_index("ix_mps_bid_auction_id", "mps_bid", ["auction_id"])
        op.create_index("ix_mps_bid_bidder_user_id", "mps_bid", ["bidder_user_id"])

    # mps_auction_watcher
    if not _table_exists("mps_auction_watcher"):
        op.create_table(
            "mps_auction_watcher",
            sa.Column("id", UUID, nullable=False),
            sa.Column("auction_id", UUID, nullable=False),
            sa.Column("user_id", UUID, nullable=False),
            sa.Column("created_timestamp", sa.BigInteger(), nullable=False),
            sa.Column("notify_on_outbid", sa.Boolean(), server_default="1", nullable=False),
            sa.Column("notify_on_ending_soon", sa.Boolean(), server_default="1", nullable=False),
            sa.ForeignKeyConstraint(["auction_id"], ["mps_auction.id"]),
            sa.ForeignKeyConstraint(["user_id"], ["mps_user.id"]),
            sa.PrimaryKeyConstraint("id"),
        )
        op.create_index("ix_mps_auction_watcher_id", "mps_auction_watcher", ["id"])
        op.create_index("ix_mps_auction_watcher_auction_id", "mps_auction_watcher", ["auction_id"])
        op.create_index("ix_mps_auction_watcher_user_id", "mps_auction_watcher", ["user_id"])

    # mps_offer
    if not _table_exists("mps_offer"):
        op.create_table(
            "mps_offer",
            sa.Column("id", UUID, nullable=False),
            sa.Column("product_id", UUID, nullable=False),
            sa.Column("shop_id", UUID, nullable=False),
            sa.Column("buyer_user_id", UUID, nullable=False),
            sa.Column("state", sa.Integer(), server_default="0", nullable=False),
            sa.Column("current_amount_in_cents", sa.BigInteger(), nullable=False),
            sa.Column("current_party", sa.Integer(), server_default="1", nullable=False),
            sa.Column("created_timestamp", sa.BigInteger(), nullable=False),
            sa.Column("last_action_timestamp", sa.BigInteger(), nullable=False),
            sa.Column("expires_timestamp", sa.BigInteger(), nullable=False),
            sa.Column("paid_timestamp", sa.BigInteger(), nullable=True),
            sa.Column("round_count", sa.Integer(), server_default="0", nullable=False),
            sa.Column("buyer_message", sa.UnicodeText(), nullable=True),
            sa.Column("seller_message", sa.UnicodeText(), nullable=True),
            sa.ForeignKeyConstraint(["buyer_user_id"], ["mps_user.id"]),
            sa.ForeignKeyConstraint(["product_id"], ["mps_product.id"]),
            sa.ForeignKeyConstraint(["shop_id"], ["mps_shop.id"]),
            sa.PrimaryKeyConstraint("id"),
        )
        op.create_index("ix_mps_offer_id", "mps_offer", ["id"])
        op.create_index("ix_mps_offer_product_id", "mps_offer", ["product_id"])
        op.create_index("ix_mps_offer_shop_id", "mps_offer", ["shop_id"])
        op.create_index("ix_mps_offer_buyer_user_id", "mps_offer", ["buyer_user_id"])

    # mps_offer_event
    if not _table_exists("mps_offer_event"):
        op.create_table(
            "mps_offer_event",
            sa.Column("id", UUID, nullable=False),
            sa.Column("offer_id", UUID, nullable=False),
            sa.Column("actor_user_id", UUID, nullable=True),
            sa.Column("event_type", sa.Integer(), nullable=False),
            sa.Column("amount_in_cents", sa.BigInteger(), nullable=True),
            sa.Column("message", sa.UnicodeText(), nullable=True),
            sa.Column("created_timestamp", sa.BigInteger(), nullable=False),
            sa.ForeignKeyConstraint(["actor_user_id"], ["mps_user.id"]),
            sa.ForeignKeyConstraint(["offer_id"], ["mps_offer.id"]),
            sa.PrimaryKeyConstraint("id"),
        )
        op.create_index("ix_mps_offer_event_id", "mps_offer_event", ["id"])
        op.create_index("ix_mps_offer_event_offer_id", "mps_offer_event", ["offer_id"])


def downgrade():
    if _table_exists("mps_offer_event"):
        op.drop_index("ix_mps_offer_event_offer_id", table_name="mps_offer_event")
        op.drop_index("ix_mps_offer_event_id", table_name="mps_offer_event")
        op.drop_table("mps_offer_event")
    if _table_exists("mps_offer"):
        op.drop_index("ix_mps_offer_buyer_user_id", table_name="mps_offer")
        op.drop_index("ix_mps_offer_shop_id", table_name="mps_offer")
        op.drop_index("ix_mps_offer_product_id", table_name="mps_offer")
        op.drop_index("ix_mps_offer_id", table_name="mps_offer")
        op.drop_table("mps_offer")
    if _table_exists("mps_auction_watcher"):
        op.drop_index("ix_mps_auction_watcher_user_id", table_name="mps_auction_watcher")
        op.drop_index("ix_mps_auction_watcher_auction_id", table_name="mps_auction_watcher")
        op.drop_index("ix_mps_auction_watcher_id", table_name="mps_auction_watcher")
        op.drop_table("mps_auction_watcher")
    if _table_exists("mps_bid"):
        op.drop_index("ix_mps_bid_bidder_user_id", table_name="mps_bid")
        op.drop_index("ix_mps_bid_auction_id", table_name="mps_bid")
        op.drop_index("ix_mps_bid_id", table_name="mps_bid")
        op.drop_table("mps_bid")
    if _table_exists("mps_auction"):
        op.drop_index("ix_mps_auction_shop_id", table_name="mps_auction")
        op.drop_index("ix_mps_auction_product_id", table_name="mps_auction")
        op.drop_index("ix_mps_auction_id", table_name="mps_auction")
        op.drop_table("mps_auction")

    for col in (
        "offer_min_buyer_account_age_hours",
        "offer_max_rounds",
        "offer_expiration_hours",
        "offer_auto_decline_threshold_pct",
        "offer_auto_accept_threshold_pct",
        "offer_min_in_cents",
        "offer_enabled",
    ):
        if _column_exists("mps_shop", col):
            op.drop_column("mps_shop", col)
    if _column_exists("mps_product", "allow_offers"):
        op.drop_column("mps_product", "allow_offers")
    if _column_exists("mps_product", "pricing_mode"):
        op.drop_column("mps_product", "pricing_mode")
