from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Table, Float, Boolean, Index, text, DDL, event, UniqueConstraint, ForeignKeyConstraint
from sqlalchemy.sql import func
from app.db.database import Base
from sqlalchemy.orm import relationship

# =============================================================================
# Core Models
# =============================================================================

class MTGJSONSKU(Base):
    """Represents the most granular level of card identification.
        THIS WORKS EVEN IF ITS A BOX SOMEHOW
    """
    __tablename__ = "mtgjson_skus"

    id = Column(Integer, primary_key=True, index=True)
    mtgjson_uuid = Column(String, ForeignKey("mtgjson_cards.mtgjson_uuid"), unique=True, index=True)
    tcgplayer_sku_id = Column(Integer, index=True, unique=True)
    tcgplayer_product_id = Column(Integer, nullable=False)
    normalized_printing = Column(String, nullable=False)  # normalized for FK
    condition = Column(String) # for boxes, condition = unopened
    finish = Column(String, nullable=True) # TODO MAKE THESE ENUMS
    language = Column(String)
    printing = Column(String)  # original unnormalized field ##### for boxes, printing = NON FOIL
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), onupdate=func.now())

    # Foreign key to tcgplayer_products via composite key
    __table_args__ = (
        ForeignKeyConstraint(
            ["tcgplayer_product_id", "normalized_printing"],
            ["tcgplayer_products.tcgplayer_product_id", "tcgplayer_products.normalized_sub_type_name"],
            name="fk_sku_to_product_composite"
        ),
        Index('idx_sku_product_printing', 'tcgplayer_product_id', 'normalized_printing', unique=True),
    )

    product = relationship("TCGPlayerProduct", back_populates="skus")
    physical_items = relationship("PhysicalItem", back_populates="sku", primaryjoin="PhysicalItem.tcgplayer_sku_id == MTGJSONSKU.tcgplayer_sku_id")

    card = relationship("MTGJSONCard", back_populates="skus", primaryjoin="MTGJSONCard.mtgjson_uuid == MTGJSONSKU.mtgjson_uuid")


class MTGJSONCard(Base):
    """Represents a Magic: The Gathering card."""
    __tablename__ = "mtgjson_cards"

    id = Column(Integer, primary_key=True, index=True)
    mtgjson_uuid = Column(String, ForeignKey("mtgjson_cards.mtgjson_uuid"), unique=True, index=True)
    name = Column(String)
    set_code = Column(String)
    abu_id = Column(String, nullable=True)
    card_kingdom_etched_id = Column(String, nullable=True)
    card_kingdom_foil_id = Column(String, nullable=True)
    card_kingdom_id = Column(String, nullable=True)
    cardsphere_id = Column(String, nullable=True)
    cardsphere_foil_id = Column(String, nullable=True)
    cardtrader_id = Column(String, nullable=True)
    csi_id = Column(String, nullable=True)
    mcm_id = Column(String, nullable=True)
    mcm_meta_id = Column(String, nullable=True)
    miniaturemarket_id = Column(String, nullable=True)
    mtg_arena_id = Column(String, nullable=True)
    mtgjson_foil_version_id = Column(String, nullable=True)
    mtgjson_non_foil_version_id = Column(String, nullable=True)
    mtgjson_v4_id = Column(String, nullable=True)
    mtgo_foil_id = Column(String, nullable=True)
    mtgo_id = Column(String, nullable=True)
    multiverse_id = Column(String, nullable=True)
    scg_id = Column(String, nullable=True)
    scryfall_id = Column(String, nullable=True)
    scryfall_card_back_id = Column(String, nullable=True)
    scryfall_oracle_id = Column(String, nullable=True)
    scryfall_illustration_id = Column(String, nullable=True)
    tcgplayer_product_id = Column(String, nullable=True)
    tcgplayer_etched_product_id = Column(String, nullable=True)
    tnt_id = Column(String, nullable=True)
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), onupdate=func.now())

    skus = relationship("MTGJSONSKU", back_populates="card", primaryjoin="MTGJSONSKU.mtgjson_uuid == MTGJSONCard.mtgjson_uuid")


class TCGPlayerProduct(Base):
    """Represents a higher-level TCGPlayer product concept."""
    __tablename__ = "tcgplayer_products"

    id = Column(Integer, primary_key=True, index=True)
    tcgplayer_product_id = Column(Integer, nullable=False)
    normalized_sub_type_name = Column(String, nullable=False)  # normalized for FK
    sub_type_name = Column(String)  # original unnormalized field

    name = Column(String, nullable=False)
    clean_name = Column(String)
    image_url = Column(String)
    category_id = Column(Integer, ForeignKey("tcgplayer_categories.category_id"))
    group_id = Column(Integer, ForeignKey("tcgplayer_groups.group_id"))
    url = Column(String)
    modified_on = Column(DateTime)
    image_count = Column(Integer)
    ext_rarity = Column(String)
    ext_subtype = Column(String)
    ext_oracle_text = Column(String)
    ext_number = Column(String)
    low_price = Column(Float)
    mid_price = Column(Float)
    high_price = Column(Float)
    market_price = Column(Float)
    direct_low_price = Column(Float)
    ext_power = Column(String)
    ext_toughness = Column(String)
    ext_flavor_text = Column(String)

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

    # Enforce uniqueness for composite key
    __table_args__ = (
        UniqueConstraint("tcgplayer_product_id", "normalized_sub_type_name", name="uq_product_subtype"),
    )

    # Backref to SKUs that link via composite FK
    skus = relationship("MTGJSONSKU", back_populates="product")
    physical_items_direct = relationship("PhysicalItem", 
    back_populates="product_direct", 
    primaryjoin="foreign(TCGPlayerProduct.tcgplayer_product_id) == PhysicalItem.tcgplayer_product_id")
    category = relationship("TCGPlayerCategory", back_populates="products")
    group = relationship("TCGPlayerGroup", back_populates="products")
    price_history = relationship("TCGPlayerPriceHistory", 
        back_populates="product",
        primaryjoin="and_(foreign(TCGPlayerProduct.tcgplayer_product_id) == TCGPlayerPriceHistory.product_id, "
                "foreign(TCGPlayerProduct.sub_type_name) == TCGPlayerPriceHistory.sub_type_name)")

    most_recent_tcgplayer_price = relationship("MostRecentTCGPlayerPrice", 
        back_populates="product",
        primaryjoin="and_(foreign(TCGPlayerProduct.tcgplayer_product_id) == MostRecentTCGPlayerPrice.product_id, "
                "foreign(TCGPlayerProduct.sub_type_name) == MostRecentTCGPlayerPrice.sub_type_name)")

# =============================================================================
# Supporting Models
# =============================================================================

class TCGPlayerCategory(Base):
    """Represents a TCGPlayer product category."""
    __tablename__ = "tcgplayer_categories"

    id = Column(Integer, primary_key=True, index=True)
    category_id = Column(Integer, unique=True, index=True)
    name = Column(String, nullable=False)
    display_name = Column(String)
    seo_category_name = Column(String)
    category_description = Column(String)
    category_page_title = Column(String)
    sealed_label = Column(String)
    non_sealed_label = Column(String)
    condition_guide_url = Column(String)
    is_scannable = Column(Boolean, default=False)
    popularity = Column(Integer, default=0)
    is_direct = Column(Boolean, default=False)
    modified_on = Column(DateTime)
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), onupdate=func.now())

    products = relationship("TCGPlayerProduct", back_populates="category")

class TCGPlayerGroup(Base):
    """Represents a TCGPlayer product group."""
    __tablename__ = "tcgplayer_groups"

    id = Column(Integer, primary_key=True, index=True)
    group_id = Column(Integer, unique=True, index=True)
    name = Column(String, nullable=False)
    abbreviation = Column(String)
    is_supplemental = Column(Boolean, default=False)
    published_on = Column(DateTime)
    modified_on = Column(DateTime)
    category_id = Column(Integer, ForeignKey("tcgplayer_categories.category_id"))
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), onupdate=func.now())

    products = relationship("TCGPlayerProduct", back_populates="group")

class TCGPlayerPriceHistory(Base):
    """Represents the price history for a product variant (foil/non-foil).
    
    Each product has exactly two price history records - one for foil and one for non-foil.
    The relationship to TCGPlayerProduct is effectively 1:1 when considering both product_id
    and sub_type_name.
    """
    __tablename__ = "tcgplayer_price_history"

    id = Column(Integer, primary_key=True, index=True)
    product_id = Column(Integer, nullable=False, index=True)
    sub_type_name = Column(String, index=True)  # This indicates foil/non-foil
    date = Column(DateTime, index=True)
    low_price = Column(Float)
    mid_price = Column(Float)
    high_price = Column(Float)
    market_price = Column(Float)
    direct_low_price = Column(Float)
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), onupdate=func.now())

    # Add a unique constraint on (product_id, sub_type_name, date) to prevent duplicate entries
    __table_args__ = (
        Index('idx_price_history_product_subtype_date', 'product_id', 'sub_type_name', 'date'),
    )

    product = relationship("TCGPlayerProduct", 
        back_populates="price_history",
        primaryjoin="and_(TCGPlayerPriceHistory.product_id == foreign(TCGPlayerProduct.tcgplayer_product_id), "
                "TCGPlayerPriceHistory.sub_type_name == foreign(TCGPlayerProduct.sub_type_name))")

class MostRecentTCGPlayerPrice(Base):
    """Represents the most recent price for a product.
    
    This is a materialized view that contains the latest price data for each product.
    It is maintained through triggers on the price_history table.
    """
    __tablename__ = "most_recent_tcgplayer_price"
    
    id = Column(Integer, primary_key=True)
    product_id = Column(Integer, nullable=False, index=True)
    sub_type_name = Column(String, index=True)  # This indicates foil/non-foil
    date = Column(DateTime, nullable=False)
    low_price = Column(Float)
    mid_price = Column(Float)
    high_price = Column(Float)
    market_price = Column(Float)
    direct_low_price = Column(Float)
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), onupdate=func.now())

    __table_args__ = (
    Index('idx_most_recent_price_product_subtype', 'product_id', 'sub_type_name', unique=True),
    {'info': {'is_view': True}}
)


    product = relationship("TCGPlayerProduct", 
        back_populates="most_recent_tcgplayer_price",
        primaryjoin="and_(MostRecentTCGPlayerPrice.product_id == foreign(TCGPlayerProduct.tcgplayer_product_id), "
                "MostRecentTCGPlayerPrice.sub_type_name == foreign(TCGPlayerProduct.sub_type_name))")

def create_most_recent_price_view():
    """Creates the materialized view for most recent prices."""
    return DDL("""
        CREATE MATERIALIZED VIEW IF NOT EXISTS most_recent_tcgplayer_price AS
        SELECT DISTINCT ON (ph.product_id, ph.sub_type_name)
            ph.id,
            ph.product_id,
            ph.sub_type_name,
            ph.date,
            ph.low_price,
            ph.mid_price,
            ph.high_price,
            ph.market_price,
            ph.direct_low_price,
            ph.created_at,
            ph.updated_at
        FROM tcgplayer_price_history ph
        ORDER BY ph.product_id, ph.sub_type_name, ph.date DESC;
        
        CREATE UNIQUE INDEX IF NOT EXISTS idx_most_recent_price_product_subtype 
        ON most_recent_tcgplayer_price (product_id, sub_type_name);
    """)

# Register the view creation with SQLAlchemy
event.listen(
    MostRecentTCGPlayerPrice.__table__,
    'after_create',
    create_most_recent_price_view()
)

# Add a method to refresh the view
@classmethod
def refresh_view(cls, session):
    """Refreshes the materialized view."""
    session.execute(text("REFRESH MATERIALIZED VIEW CONCURRENTLY most_recent_tcgplayer_price"))
    session.commit()

MostRecentTCGPlayerPrice.refresh_view = refresh_view