from sqlalchemy import Column, Integer, String, Float, DateTime, ForeignKey, CheckConstraint
from sqlalchemy.orm import relationship
from app.db.database import Base
from sqlalchemy import event
from sqlalchemy.orm import Session
from sqlalchemy import func
from sqlalchemy.ext.hybrid import hybrid_property
from datetime import datetime
from app.models.critical_error_log import CriticalErrorLog

class PhysicalItem(Base):
    __tablename__ = "physical_items"
    
    id = Column(Integer, primary_key=True)
    item_type = Column(String)

    # at least one of these must be set to pass the constraint
    tcgplayer_product_id = Column(Integer, nullable=True)
    tcgplayer_sku_id = Column(Integer, ForeignKey("mtgjson_skus.tcgplayer_sku_id"), nullable=True)

    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
    deleted_at = Column(DateTime(timezone=True), nullable=True)

    __table_args__ = (
        CheckConstraint(
            "(tcgplayer_sku_id IS NOT NULL OR tcgplayer_product_id IS NOT NULL)",
            name="ck_physical_items_sku_or_product_not_null"
        ),
    )

    __mapper_args__ = {
        'polymorphic_on': item_type,
        'polymorphic_identity': 'physical_item'
    }

    # Relationships
    sku = relationship("MTGJSONSKU", back_populates="physical_items", primaryjoin="PhysicalItem.tcgplayer_sku_id == MTGJSONSKU.tcgplayer_sku_id")
    product_direct = relationship("TCGPlayerProduct", 
    back_populates="physical_items_direct", 
    primaryjoin="PhysicalItem.tcgplayer_product_id == foreign(TCGPlayerProduct.tcgplayer_product_id)")
    inventory_item = relationship("InventoryItem", uselist=False, back_populates="physical_item")
    transaction_items = relationship("TransactionItem", back_populates="physical_item")

    @hybrid_property
    def products(self):
        """
        Dynamically resolve the associated TCGPlayerProduct(s):
        - If the SKU is set, return all linked products.
        - Else, return a list containing a single product from direct link.
        """
        # TODO IS THIS EVEN CORRECT OH GOD
        if self.sku and self.sku.products:
            return self.sku.products  # This is a list of TCGPlayerProduct
        return [self.product_direct] if self.product_direct else []

class InventoryItem(Base):
    __tablename__ = "inventory_items"

    id = Column(Integer, primary_key=True, index=True)
    physical_item_id = Column(Integer, ForeignKey("physical_items.id"), unique=True)
    cost_basis = Column(Float)
    parent_id = Column(Integer, ForeignKey("inventory_items.id"), nullable=True)
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
    deleted_at = Column(DateTime(timezone=True), nullable=True)

    # Relationships
    physical_item = relationship("PhysicalItem", back_populates="inventory_item")
    parent = relationship("InventoryItem", remote_side=[id], back_populates="children")
    children = relationship("InventoryItem", back_populates="parent", overlaps="parent")
    marketplace_listings = relationship("MarketplaceListing", back_populates="inventory_item")

    @property
    def products(self):
        """
        Proxy access to the associated TCGPlayerProduct(s) via the linked PhysicalItem.
        Returns:
            list[TCGPlayerProduct] or [] if no physical item or no linked products.
        """
        return self.physical_item.product if self.physical_item else []
    
    def soft_delete(self, timestamp=None):
        if not timestamp:
            timestamp = datetime.now()
        self.deleted_at = timestamp
        for child in self.children:
            child.soft_delete(timestamp)



class SealedBox(PhysicalItem):
    __tablename__ = "sealed_boxes"

    id = Column(Integer, ForeignKey('physical_items.id'), primary_key=True)
    case_id = Column(Integer, ForeignKey("sealed_cases.id"), nullable=True)
    expected_value = Column(Float)

    __mapper_args__ = {
        'polymorphic_identity': 'sealed_box'
    }

    # Relationships
    case = relationship("SealedCase", back_populates="boxes", foreign_keys=[case_id])
    open_event = relationship("OpenEvent", uselist=False, back_populates="sealed_box")

class SealedCase(PhysicalItem):
    __tablename__ = "sealed_cases"

    id = Column(Integer, ForeignKey('physical_items.id'), primary_key=True)
    expected_value = Column(Float)
    num_boxes = Column(Integer)

    __mapper_args__ = {
        'polymorphic_identity': 'sealed_case'
    }

    # Relationships
    boxes = relationship("SealedBox", back_populates="case", foreign_keys=[SealedBox.case_id])
    open_event = relationship("OpenEvent", uselist=False, back_populates="sealed_case")


class OpenEvent(Base):
    __tablename__ = "open_events"

    id = Column(Integer, primary_key=True, index=True)
    sealed_case_id = Column(Integer, ForeignKey("sealed_cases.id"), nullable=True)
    sealed_box_id = Column(Integer, ForeignKey("sealed_boxes.id"), nullable=True)
    open_date = Column(DateTime(timezone=True))
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
    deleted_at = Column(DateTime(timezone=True), nullable=True)

    # Relationships
    sealed_case = relationship("SealedCase", back_populates="open_event")
    sealed_box = relationship("SealedBox", back_populates="open_event")
    resulting_boxes = relationship("OpenBox", back_populates="open_event")
    resulting_cards = relationship("OpenCard", back_populates="open_event")

class OpenCard(PhysicalItem):
    __tablename__ = "open_cards"

    id = Column(Integer, ForeignKey('physical_items.id'), primary_key=True)
    open_event_id = Column(Integer, ForeignKey("open_events.id"))
    box_id = Column(Integer, ForeignKey("open_boxes.id"), nullable=True)

    __mapper_args__ = {
        'polymorphic_identity': 'open_card'
    }

    # Relationships
    open_event = relationship("OpenEvent", back_populates="resulting_cards")
    box = relationship("OpenBox", back_populates="cards", foreign_keys=[box_id])

class OpenBox(PhysicalItem):
    __tablename__ = "open_boxes"

    id = Column(Integer, ForeignKey('physical_items.id'), primary_key=True)
    open_event_id = Column(Integer, ForeignKey("open_events.id"))
    sealed_box_id = Column(Integer, ForeignKey("sealed_boxes.id"))

    __mapper_args__ = {
        'polymorphic_identity': 'open_box'
    }

    # Relationships
    open_event = relationship("OpenEvent", back_populates="resulting_boxes")
    sealed_box = relationship("SealedBox", foreign_keys=[sealed_box_id])
    cards = relationship("OpenCard", back_populates="box", foreign_keys=[OpenCard.box_id])




class SealedExpectedValue(Base):
    __tablename__ = "sealed_expected_values"

    id = Column(Integer, primary_key=True, index=True)
    tcgplayer_product_id = Column(Integer, nullable=False)
    expected_value = Column(Float, nullable=False)
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
    deleted_at = Column(DateTime(timezone=True), nullable=True)

    # Relationships
    product = relationship(
        "TCGPlayerProduct",
        primaryjoin="SealedExpectedValue.tcgplayer_product_id == foreign(TCGPlayerProduct.tcgplayer_product_id)",
        viewonly=True,
        backref="sealed_expected_values"
    )

# helper for ev
def assign_expected_value(target, session):
    products = target.product  # uses hybrid property
    if not products:
        raise ValueError(f"No product found for item ID {target.id}")
    
    if len(products) > 1:
        product_names = [p.name for p in products]
        critical_error = CriticalErrorLog(
            error_type="multiple_products_found",
            error_message=f"Multiple products found when assigning expected value for item ID {target.id} product names {product_names}"
        )
        session.add(critical_error)
        session.commit()
        raise ValueError(f"Multiple products found when assigning expected value for item ID {target.id} product names {product_names}")

    product_id = products[0].tcgplayer_product_id  # reliable lookup key

    expected_value_entry = session.query(SealedExpectedValue).filter(
        SealedExpectedValue.tcgplayer_product_id == product_id,
        SealedExpectedValue.deleted_at == None
    ).order_by(SealedExpectedValue.created_at.desc()).first()

    if expected_value_entry:
        target.expected_value = expected_value_entry.expected_value
    else:
        critical_error = CriticalErrorLog(
            error_type="no_expected_value_found",
            error_message=f"No expected value found for product {products[0].name}"
        )
        session.add(critical_error)
        session.commit()
        raise ValueError(f"No expected value found for product {products[0].name}")


# event listeners
@event.listens_for(SealedBox, 'before_insert')
def sealed_box_before_insert(mapper, connection, target):
    session = Session.object_session(target)
    if session:
        assign_expected_value(target, session)

@event.listens_for(SealedCase, 'before_insert')
def sealed_case_before_insert(mapper, connection, target):
    session = Session.object_session(target)
    if session:
        assign_expected_value(target, session)

class TransactionItem(Base):
    __tablename__ = "transaction_items"

    id = Column(Integer, primary_key=True, index=True)
    transaction_id = Column(Integer, ForeignKey("transactions.id"))
    physical_item_id = Column(Integer, ForeignKey("physical_items.id"))
    unit_price = Column(Float, nullable=False)
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
    deleted_at = Column(DateTime(timezone=True), nullable=True)

    # Relationships
    transaction = relationship("Transaction", back_populates="transaction_items")
    physical_item = relationship("PhysicalItem", back_populates="transaction_items")

class Vendor(Base):
    __tablename__ = "vendors"

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, index=True)
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
    deleted_at = Column(DateTime(timezone=True), nullable=True)

class Customer(Base):
    __tablename__ = "customers"

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, index=True)
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
    deleted_at = Column(DateTime(timezone=True), nullable=True)
class Transaction(Base):
    __tablename__ = "transactions"

    id = Column(Integer, primary_key=True, index=True)
    vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=True)
    customer_id = Column(Integer, ForeignKey("customers.id"), nullable=True)
    marketplace_id = Column(Integer, ForeignKey("marketplaces.id"), nullable=True)
    transaction_type = Column(String)  # 'purchase' or 'sale'
    transaction_date = Column(DateTime(timezone=True))
    transaction_total_amount = Column(Float)
    transaction_notes = Column(String)
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
    deleted_at = Column(DateTime(timezone=True), nullable=True)

    # Relationships
    transaction_items = relationship("TransactionItem", back_populates="transaction")

class Marketplace(Base):
    __tablename__ = "marketplaces"

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, index=True)
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
    deleted_at = Column(DateTime(timezone=True), nullable=True)
    
    # Relationships
    listings = relationship("MarketplaceListing", back_populates="marketplace")

class MarketplaceListing(Base):
    __tablename__ = "marketplace_listings"

    id = Column(Integer, primary_key=True, index=True)
    inventory_item_id = Column(Integer, ForeignKey("inventory_items.id"), nullable=False)
    marketplace_id = Column(Integer, ForeignKey("marketplaces.id"), nullable=False)
    listing_date = Column(DateTime(timezone=True))
    delisting_date = Column(DateTime(timezone=True), nullable=True)
    listed_price = Column(Float)
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
    deleted_at = Column(DateTime(timezone=True), nullable=True)

    # Relationships
    inventory_item = relationship("InventoryItem", back_populates="marketplace_listings")
    marketplace = relationship("Marketplace", back_populates="listings")