"""Auction models — MPS-20.

An auction is a Product whose price is determined by competitive bidding
within a time window. The Product carries `pricing_mode` (0=fixed, 1=auction,
2=auction+buy_now); MpsAuction carries the auction-specific fields and
state machine; MpsBid is one bid per (auction, bidder, attempt); and
MpsAuctionWatcher lets users follow auctions for notifications.

State machine (auction.state):
    0 draft     — owner editing, not visible
    1 scheduled — countdown to start_timestamp
    2 active    — bidding open
    3 ended     — bidding closed; winner determined or reserve not met
    4 settled   — winner paid via cart; product transferred
    5 cancelled — owner aborted (pre-active only without admin override)

Soft-close: a bid placed within `soft_close_seconds` of `end_timestamp`
extends `end_timestamp` by `soft_close_seconds`. `original_end_timestamp`
preserves the scheduled close for audit.

See `lib/auction.py` for pure-function bid/proxy/soft-close logic.
"""

import uuid

from sqlalchemy import Column, BigInteger, Boolean, Integer, Unicode
from sqlalchemy.orm import relationship, backref

from .meta import (
    Base,
    RBase,
    UUIDType,
    foreign_key,
    now_timestamp,
    get_object_by_id,
)

from ..lib.currency import cents_to_dollars


# Auction state constants — mirror VISIBILITY_INT_TO_HUMAN style.
AUCTION_STATE_DRAFT = 0
AUCTION_STATE_SCHEDULED = 1
AUCTION_STATE_ACTIVE = 2
AUCTION_STATE_ENDED = 3
AUCTION_STATE_SETTLED = 4
AUCTION_STATE_CANCELLED = 5

AUCTION_STATE_INT_TO_HUMAN = {
    AUCTION_STATE_DRAFT: "Draft",
    AUCTION_STATE_SCHEDULED: "Scheduled",
    AUCTION_STATE_ACTIVE: "Active",
    AUCTION_STATE_ENDED: "Ended",
    AUCTION_STATE_SETTLED: "Settled",
    AUCTION_STATE_CANCELLED: "Cancelled",
}


# Default soft-close window in seconds (mirrors eBay's last-minute extension).
DEFAULT_SOFT_CLOSE_SECONDS = 60

# Default bid increment as a percentage of current high (5%). The model
# stores this as cents — view layer computes default from listed price.
DEFAULT_BID_INCREMENT_IN_CENTS = 100


class MpsAuction(RBase, Base):
    """One auction per Product (1:1). Created when shop owner enables
    pricing_mode=1 or 2 on a product."""

    id = Column(UUIDType, primary_key=True, index=True)
    product_id = Column(
        UUIDType,
        foreign_key("Product", "id"),
        nullable=False,
        unique=True,
        index=True,
    )
    shop_id = Column(UUIDType, foreign_key("Shop", "id"), nullable=False, index=True)

    state = Column(
        Integer,
        nullable=False,
        default=AUCTION_STATE_DRAFT,
        server_default=str(AUCTION_STATE_DRAFT),
    )

    start_timestamp = Column(BigInteger, nullable=True)
    end_timestamp = Column(BigInteger, nullable=True)
    original_end_timestamp = Column(BigInteger, nullable=True)

    start_price_in_cents = Column(BigInteger, nullable=False, default=0)
    reserve_price_in_cents = Column(BigInteger, nullable=True)
    buy_now_price_in_cents = Column(BigInteger, nullable=True)
    bid_increment_in_cents = Column(
        BigInteger, nullable=False, default=DEFAULT_BID_INCREMENT_IN_CENTS
    )
    soft_close_seconds = Column(
        Integer, nullable=False, default=DEFAULT_SOFT_CLOSE_SECONDS
    )

    # MPS-20: how many units this auction sells (lot size). Default 1.
    # For physical products with N inventory, owner can auction K units
    # (K <= N) while keeping the other N-K at list price (mode 2).
    # Digital products force quantity=1 — auctioning multiple copies of
    # a digital file makes no sense (no scarcity).
    quantity = Column(
        Integer, nullable=False, default=1, server_default="1"
    )

    winner_user_id = Column(UUIDType, foreign_key("User", "id"), nullable=True)
    winning_bid_id = Column(UUIDType, nullable=True)
    payment_deadline_timestamp = Column(BigInteger, nullable=True)

    currency = Column(Unicode(3), nullable=False, default="USD")

    created_timestamp = Column(BigInteger, nullable=False)
    updated_timestamp = Column(BigInteger, nullable=False)

    product = relationship(
        argument="Product",
        uselist=False,
        backref=backref("auction", uselist=False),
    )
    shop = relationship(argument="Shop", uselist=False)
    winner = relationship(argument="User", uselist=False, foreign_keys=[winner_user_id])

    bids = relationship(
        argument="MpsBid",
        lazy="dynamic",
        order_by="MpsBid.created_timestamp.desc()",
        back_populates="auction",
        cascade="all, delete-orphan",
    )
    watchers = relationship(
        argument="MpsAuctionWatcher",
        lazy="dynamic",
        back_populates="auction",
        cascade="all, delete-orphan",
    )

    def __init__(self, product, shop, start_price_in_cents=0,
                 bid_increment_in_cents=DEFAULT_BID_INCREMENT_IN_CENTS,
                 soft_close_seconds=DEFAULT_SOFT_CLOSE_SECONDS,
                 currency="USD"):
        self.id = uuid.uuid1()
        self.product = product
        self.shop = shop
        self.state = AUCTION_STATE_DRAFT
        self.start_price_in_cents = start_price_in_cents
        self.bid_increment_in_cents = bid_increment_in_cents
        self.soft_close_seconds = soft_close_seconds
        self.currency = currency
        self.created_timestamp = now_timestamp()
        self.updated_timestamp = self.created_timestamp

    @property
    def is_lot_auction(self):
        """True when this auction sells more than 1 unit at once."""
        return self.quantity > 1

    @property
    def is_draft(self):
        return self.state == AUCTION_STATE_DRAFT

    @property
    def is_scheduled(self):
        return self.state == AUCTION_STATE_SCHEDULED

    @property
    def is_active(self):
        return self.state == AUCTION_STATE_ACTIVE

    @property
    def is_ended(self):
        return self.state == AUCTION_STATE_ENDED

    @property
    def is_settled(self):
        return self.state == AUCTION_STATE_SETTLED

    @property
    def is_cancelled(self):
        return self.state == AUCTION_STATE_CANCELLED

    @property
    def is_terminal(self):
        return self.state in (
            AUCTION_STATE_SETTLED,
            AUCTION_STATE_CANCELLED,
        )

    @property
    def state_human(self):
        return AUCTION_STATE_INT_TO_HUMAN.get(self.state, "Unknown")

    @property
    def time_remaining_ms(self):
        """Milliseconds until end_timestamp; 0 if past or unset."""
        if not self.end_timestamp:
            return 0
        return max(0, self.end_timestamp - now_timestamp())

    @property
    def has_buy_now(self):
        return self.buy_now_price_in_cents is not None

    @property
    def has_reserve(self):
        return self.reserve_price_in_cents is not None

    @property
    def current_high_in_cents(self):
        """Highest bid amount; falls back to start_price when no bids."""
        top = (
            self.bids.order_by(None)
            .order_by(MpsBid.amount_in_cents.desc())
            .first()
        )
        if top is None:
            return self.start_price_in_cents
        return top.amount_in_cents

    @property
    def reserve_met(self):
        if not self.has_reserve:
            return True
        return self.current_high_in_cents >= self.reserve_price_in_cents

    @property
    def start_price(self):
        return cents_to_dollars(self.start_price_in_cents)

    @property
    def reserve_price(self):
        if self.reserve_price_in_cents is None:
            return None
        return cents_to_dollars(self.reserve_price_in_cents)

    @property
    def buy_now_price(self):
        if self.buy_now_price_in_cents is None:
            return None
        return cents_to_dollars(self.buy_now_price_in_cents)

    @property
    def bid_increment(self):
        return cents_to_dollars(self.bid_increment_in_cents)

    @property
    def current_high(self):
        return cents_to_dollars(self.current_high_in_cents)

    @property
    def min_next_bid_in_cents(self):
        """Smallest bid that would be accepted right now."""
        if self.bids.count() == 0:
            return self.start_price_in_cents
        return self.current_high_in_cents + self.bid_increment_in_cents

    @property
    def min_next_bid(self):
        return cents_to_dollars(self.min_next_bid_in_cents)


class MpsBid(RBase, Base):
    """One bid on an auction. amount_in_cents is the actual bid; max_proxy
    is the bidder's secret ceiling, used by lib/auction proxy resolution."""

    id = Column(UUIDType, primary_key=True, index=True)
    auction_id = Column(
        UUIDType,
        foreign_key("MpsAuction", "id"),
        nullable=False,
        index=True,
    )
    bidder_user_id = Column(
        UUIDType,
        foreign_key("User", "id"),
        nullable=False,
        index=True,
    )
    amount_in_cents = Column(BigInteger, nullable=False)
    max_proxy_in_cents = Column(BigInteger, nullable=True)

    created_timestamp = Column(BigInteger, nullable=False)
    outbid_timestamp = Column(BigInteger, nullable=True)
    is_winning = Column(
        Boolean, nullable=False, default=False, server_default="0"
    )

    auction = relationship(argument="MpsAuction", uselist=False, back_populates="bids")
    bidder = relationship(argument="User", uselist=False)

    def __init__(self, auction, bidder, amount_in_cents, max_proxy_in_cents=None):
        self.id = uuid.uuid1()
        self.auction = auction
        self.bidder = bidder
        self.amount_in_cents = amount_in_cents
        self.max_proxy_in_cents = max_proxy_in_cents
        self.created_timestamp = now_timestamp()
        self.is_winning = False

    @property
    def amount(self):
        return cents_to_dollars(self.amount_in_cents)

    @property
    def max_proxy(self):
        if self.max_proxy_in_cents is None:
            return None
        return cents_to_dollars(self.max_proxy_in_cents)


class MpsAuctionWatcher(RBase, Base):
    """User watches an auction. Drives ending-soon and outbid emails."""

    id = Column(UUIDType, primary_key=True, index=True)
    auction_id = Column(
        UUIDType,
        foreign_key("MpsAuction", "id"),
        nullable=False,
        index=True,
    )
    user_id = Column(
        UUIDType,
        foreign_key("User", "id"),
        nullable=False,
        index=True,
    )
    created_timestamp = Column(BigInteger, nullable=False)
    notify_on_outbid = Column(
        Boolean, nullable=False, default=True, server_default="1"
    )
    notify_on_ending_soon = Column(
        Boolean, nullable=False, default=True, server_default="1"
    )

    auction = relationship(
        argument="MpsAuction", uselist=False, back_populates="watchers"
    )
    user = relationship(argument="User", uselist=False)

    def __init__(self, auction, user, notify_on_outbid=True,
                 notify_on_ending_soon=True):
        self.id = uuid.uuid1()
        self.auction = auction
        self.user = user
        self.notify_on_outbid = notify_on_outbid
        self.notify_on_ending_soon = notify_on_ending_soon
        self.created_timestamp = now_timestamp()


def get_auction_by_id(dbsession, auction_id):
    return get_object_by_id(dbsession, auction_id, MpsAuction)


def get_bid_by_id(dbsession, bid_id):
    return get_object_by_id(dbsession, bid_id, MpsBid)
