"""
Marketplace and economy data models.

This module defines the marketplace-related dataclasses including
MarketplaceListing, Bid, Transaction, and ShopItem for the player economy.
"""

from dataclasses import dataclass, field, asdict
from typing import Dict, Any, List, Optional
from datetime import datetime

from app.models.items import Item
from app.models.enums import ListingType, ListingStatus


@dataclass
class Bid:
    """
    Represents a bid on an auction listing.

    Attributes:
        bidder_id: User ID of the bidder
        bidder_name: Character name of the bidder
        amount: Bid amount in gold
        timestamp: ISO timestamp of when bid was placed
    """

    bidder_id: str
    bidder_name: str
    amount: int
    timestamp: str = ""

    def __post_init__(self):
        """Initialize timestamp if not provided."""
        if not self.timestamp:
            self.timestamp = datetime.utcnow().isoformat()

    def to_dict(self) -> Dict[str, Any]:
        """Serialize bid to dictionary."""
        return asdict(self)

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'Bid':
        """Deserialize bid from dictionary."""
        return cls(
            bidder_id=data["bidder_id"],
            bidder_name=data["bidder_name"],
            amount=data["amount"],
            timestamp=data.get("timestamp", ""),
        )


@dataclass
class MarketplaceListing:
    """
    Represents an item listing on the player marketplace.

    Supports both fixed-price and auction-style listings.

    Attributes:
        listing_id: Unique identifier
        seller_id: User ID of the seller
        character_id: Character ID of the seller
        item_data: Full item details being sold
        listing_type: "auction" or "fixed_price"
        price: For fixed_price listings
        starting_bid: Minimum bid for auction listings
        current_bid: Current highest bid for auction listings
        buyout_price: Optional instant-buy price for auctions
        bids: Bid history for auction listings
        auction_end: ISO timestamp when auction ends
        status: Listing status (active, sold, expired, removed)
        created_at: ISO timestamp of listing creation
    """

    listing_id: str
    seller_id: str
    character_id: str
    item_data: Item
    listing_type: ListingType
    status: ListingStatus = ListingStatus.ACTIVE
    created_at: str = ""

    # Fixed price fields
    price: int = 0

    # Auction fields
    starting_bid: int = 0
    current_bid: int = 0
    buyout_price: int = 0
    bids: List[Bid] = field(default_factory=list)
    auction_end: str = ""

    def __post_init__(self):
        """Initialize timestamps if not provided."""
        if not self.created_at:
            self.created_at = datetime.utcnow().isoformat()

    def is_auction(self) -> bool:
        """Check if this is an auction listing."""
        return self.listing_type == ListingType.AUCTION

    def is_fixed_price(self) -> bool:
        """Check if this is a fixed-price listing."""
        return self.listing_type == ListingType.FIXED_PRICE

    def is_active(self) -> bool:
        """Check if listing is active."""
        return self.status == ListingStatus.ACTIVE

    def has_ended(self) -> bool:
        """Check if auction has ended (for auction listings)."""
        if not self.is_auction() or not self.auction_end:
            return False

        end_time = datetime.fromisoformat(self.auction_end)
        return datetime.utcnow() >= end_time

    def can_bid(self, bid_amount: int) -> bool:
        """
        Check if a bid amount is valid.

        Args:
            bid_amount: Proposed bid amount

        Returns:
            True if bid is valid, False otherwise
        """
        if not self.is_auction() or not self.is_active():
            return False

        if self.has_ended():
            return False

        # First bid must meet starting bid
        if not self.bids and bid_amount < self.starting_bid:
            return False

        # Subsequent bids must exceed current bid
        if self.bids and bid_amount <= self.current_bid:
            return False

        return True

    def place_bid(self, bidder_id: str, bidder_name: str, amount: int) -> bool:
        """
        Place a bid on this auction.

        Args:
            bidder_id: User ID of bidder
            bidder_name: Character name of bidder
            amount: Bid amount

        Returns:
            True if bid was accepted, False otherwise
        """
        if not self.can_bid(amount):
            return False

        bid = Bid(
            bidder_id=bidder_id,
            bidder_name=bidder_name,
            amount=amount,
        )

        self.bids.append(bid)
        self.current_bid = amount
        return True

    def buyout(self) -> bool:
        """
        Attempt to buy out the auction immediately.

        Returns:
            True if buyout is available and successful, False otherwise
        """
        if not self.is_auction() or not self.buyout_price:
            return False

        if not self.is_active() or self.has_ended():
            return False

        self.current_bid = self.buyout_price
        self.status = ListingStatus.SOLD
        return True

    def get_winning_bidder(self) -> Optional[Bid]:
        """
        Get the current winning bid.

        Returns:
            Winning Bid or None if no bids
        """
        if not self.bids:
            return None

        # Bids are added chronologically, last one is highest
        return self.bids[-1]

    def cancel_listing(self) -> bool:
        """
        Cancel this listing (seller action).

        Returns:
            True if successfully cancelled, False if cannot be cancelled
        """
        if not self.is_active():
            return False

        # Cannot cancel auction with bids
        if self.is_auction() and self.bids:
            return False

        self.status = ListingStatus.REMOVED
        return True

    def complete_sale(self) -> None:
        """Mark listing as sold."""
        self.status = ListingStatus.SOLD

    def expire_listing(self) -> None:
        """Mark listing as expired."""
        self.status = ListingStatus.EXPIRED

    def to_dict(self) -> Dict[str, Any]:
        """Serialize listing to dictionary."""
        return {
            "listing_id": self.listing_id,
            "seller_id": self.seller_id,
            "character_id": self.character_id,
            "item_data": self.item_data.to_dict(),
            "listing_type": self.listing_type.value,
            "status": self.status.value,
            "created_at": self.created_at,
            "price": self.price,
            "starting_bid": self.starting_bid,
            "current_bid": self.current_bid,
            "buyout_price": self.buyout_price,
            "bids": [bid.to_dict() for bid in self.bids],
            "auction_end": self.auction_end,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'MarketplaceListing':
        """Deserialize listing from dictionary."""
        item_data = Item.from_dict(data["item_data"])
        listing_type = ListingType(data["listing_type"])
        status = ListingStatus(data.get("status", "active"))
        bids = [Bid.from_dict(b) for b in data.get("bids", [])]

        return cls(
            listing_id=data["listing_id"],
            seller_id=data["seller_id"],
            character_id=data["character_id"],
            item_data=item_data,
            listing_type=listing_type,
            status=status,
            created_at=data.get("created_at", ""),
            price=data.get("price", 0),
            starting_bid=data.get("starting_bid", 0),
            current_bid=data.get("current_bid", 0),
            buyout_price=data.get("buyout_price", 0),
            bids=bids,
            auction_end=data.get("auction_end", ""),
        )


@dataclass
class Transaction:
    """
    Record of a completed transaction.

    Tracks all sales for auditing and analytics.

    Attributes:
        transaction_id: Unique identifier
        buyer_id: User ID of buyer
        seller_id: User ID of seller
        listing_id: Marketplace listing ID (if from marketplace)
        item_data: Item that was sold
        price: Final sale price in gold
        timestamp: ISO timestamp of transaction
        transaction_type: "marketplace_sale", "shop_purchase", etc.
    """

    transaction_id: str
    buyer_id: str
    seller_id: str
    item_data: Item
    price: int
    transaction_type: str
    listing_id: str = ""
    timestamp: str = ""

    def __post_init__(self):
        """Initialize timestamp if not provided."""
        if not self.timestamp:
            self.timestamp = datetime.utcnow().isoformat()

    def to_dict(self) -> Dict[str, Any]:
        """Serialize transaction to dictionary."""
        return {
            "transaction_id": self.transaction_id,
            "buyer_id": self.buyer_id,
            "seller_id": self.seller_id,
            "listing_id": self.listing_id,
            "item_data": self.item_data.to_dict(),
            "price": self.price,
            "timestamp": self.timestamp,
            "transaction_type": self.transaction_type,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'Transaction':
        """Deserialize transaction from dictionary."""
        item_data = Item.from_dict(data["item_data"])

        return cls(
            transaction_id=data["transaction_id"],
            buyer_id=data["buyer_id"],
            seller_id=data["seller_id"],
            listing_id=data.get("listing_id", ""),
            item_data=item_data,
            price=data["price"],
            timestamp=data.get("timestamp", ""),
            transaction_type=data["transaction_type"],
        )


@dataclass
class ShopItem:
    """
    Item sold by NPC shops.

    Attributes:
        item_id: Item identifier
        item: Item details
        stock: Available quantity (-1 = unlimited)
        price: Fixed gold price
    """

    item_id: str
    item: Item
    stock: int = -1  # -1 = unlimited
    price: int = 0

    def is_in_stock(self) -> bool:
        """Check if item is available for purchase."""
        return self.stock != 0

    def purchase(self, quantity: int = 1) -> bool:
        """
        Attempt to purchase from stock.

        Args:
            quantity: Number of items to purchase

        Returns:
            True if purchase successful, False if insufficient stock
        """
        if self.stock == -1:  # Unlimited stock
            return True

        if self.stock < quantity:
            return False

        self.stock -= quantity
        return True

    def restock(self, quantity: int) -> None:
        """
        Add stock to this shop item.

        Args:
            quantity: Amount to add to stock
        """
        if self.stock == -1:  # Unlimited, no need to restock
            return

        self.stock += quantity

    def to_dict(self) -> Dict[str, Any]:
        """Serialize shop item to dictionary."""
        return {
            "item_id": self.item_id,
            "item": self.item.to_dict(),
            "stock": self.stock,
            "price": self.price,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'ShopItem':
        """Deserialize shop item from dictionary."""
        item = Item.from_dict(data["item"])

        return cls(
            item_id=data["item_id"],
            item=item,
            stock=data.get("stock", -1),
            price=data.get("price", 0),
        )
