from typing import List, Optional, Dict, TypedDict
from sqlalchemy.orm import Session
from app.services.base_service import BaseService
from app.models.manabox_import_staging import ManaboxImportStaging
from app.contexts.inventory_item import InventoryItemContextFactory
from app.models.inventory_management import (
    SealedBox, OpenEvent, OpenBox, OpenCard, InventoryItem, SealedCase,
    Transaction, TransactionItem, Customer, Vendor, Marketplace
)
from app.schemas.file import FileInDB
from app.schemas.transaction import PurchaseTransactionCreate, SaleTransactionCreate, TransactionResponse
from app.db.database import transaction as db_transaction
from datetime import datetime
from typing import Any
import logging

logger = logging.getLogger(__name__)

class InventoryService(BaseService):
    def __init__(self):
        super().__init__(None)
    
    async def process_manabox_import_staging(self, db: Session, manabox_file_uploads: List[FileInDB], sealed_box: SealedBox) -> bool:
        try:
            with db_transaction(db):
                # Check if box is already opened
                existing_open_event = db.query(OpenEvent).filter(
                    OpenEvent.sealed_box_id == sealed_box.id,
                    OpenEvent.deleted_at.is_(None)
                ).first()

                if existing_open_event:
                    raise ValueError(f"Box {sealed_box.id} has already been opened")

                # 1. Get the InventoryItemContext for the sealed box
                inventory_item_context = InventoryItemContextFactory(db).get_context(sealed_box.inventory_item)
                
                # 2. Create the OpenEvent
                open_event = OpenEvent(
                    sealed_box_id=sealed_box.id,
                    open_date=datetime.now(),
                    created_at=datetime.now(),
                    updated_at=datetime.now()
                )
                db.add(open_event)
                db.flush()  # Get the ID for relationships
                
                # 3. Create the OpenBox from the SealedBox
                open_box = OpenBox(
                    open_event_id=open_event.id,
                    product_id=sealed_box.product_id,
                    sealed_box_id=sealed_box.id,
                    created_at=datetime.now(),
                    updated_at=datetime.now()
                )
                db.add(open_box)
                
                # 4. Process each card from the CSV
                total_market_value = 0
                cards = []
                
                manabox_file_upload_ids = [manabox_file_upload.id for manabox_file_upload in manabox_file_uploads]

                staging_data = db.query(ManaboxImportStaging).filter(ManaboxImportStaging.file_id.in_(manabox_file_upload_ids)).all()
                
                for record in staging_data:
                    for i in range(record.quantity):
                        # Create the OpenCard
                        open_card = OpenCard(
                            product_id=record.product_id,
                            open_event_id=open_event.id,
                            box_id=open_box.id,
                            created_at=datetime.now(),
                            updated_at=datetime.now()
                        )
                        db.add(open_card)
                        
                        # Create the InventoryItem for the card
                        card_inventory_item = InventoryItem(
                            physical_item=open_card,
                            cost_basis=0,  # Will be calculated later
                            created_at=datetime.now(),
                            updated_at=datetime.now()
                        )
                        db.add(card_inventory_item)
                        
                        # Get the market value for cost basis distribution
                        card_context = InventoryItemContextFactory(db).get_context(card_inventory_item)
                        market_value = card_context.market_price
                        logger.debug(f"market_value: {market_value}")
                        total_market_value += market_value
                        
                        cards.append((open_card, card_inventory_item, market_value))
                
                # 5. Distribute the cost basis
                original_cost_basis = inventory_item_context.cost_basis
                
                for open_card, card_inventory_item, market_value in cards:
                    # Calculate this card's share of the cost basis
                    logger.debug(f"market_value: {market_value}, total_market_value: {total_market_value}, original_cost_basis: {original_cost_basis}")
                    cost_basis_share = (market_value / total_market_value) * original_cost_basis
                    card_inventory_item.cost_basis = cost_basis_share
                
                return True
                
        except Exception as e:
            raise e

    async def create_purchase_transaction(
        self,
        db: Session,
        transaction_data: PurchaseTransactionCreate
    ) -> Transaction:
        """
        Creates a purchase transaction from a vendor.
        For each item:
        1. Creates a PhysicalItem (SealedCase/SealedBox)
        2. Creates an InventoryItem with the purchase price as cost basis
        3. Creates TransactionItems linking the purchase to the items
        """
        try:
            with db_transaction(db):
                # Create the transaction
                transaction = Transaction(
                    vendor_id=transaction_data.vendor_id,
                    transaction_type='purchase',
                    transaction_date=transaction_data.transaction_date,
                    transaction_notes=transaction_data.transaction_notes,
                    created_at=datetime.now(),
                    updated_at=datetime.now()
                )
                db.add(transaction)
                db.flush()

                total_amount = 0
                physical_items = []
                for item in transaction_data.items:
                    # Create the physical item based on type
                    # TODO: remove is_case and num_boxes, should derive from product_id
                    # TODO: add support for purchasing single cards
                    if item.is_case:
                        for i in range(item.quantity):
                            physical_item = await SealedCaseService().create_sealed_case(
                                db=db,
                                product_id=item.product_id,
                                cost_basis=item.unit_price,
                                num_boxes=item.num_boxes or 1
                            )
                            physical_items.append(physical_item)
                    else:
                        for i in range(item.quantity):
                            physical_item = await SealedBoxService().create_sealed_box(
                                db=db,
                                product_id=item.product_id,
                                cost_basis=item.unit_price
                            )
                            physical_items.append(physical_item)
                    
                for physical_item in physical_items:
                    # Create transaction item
                    transaction_item = TransactionItem(
                        transaction_id=transaction.id,
                        physical_item_id=physical_item.id,
                        unit_price=item.unit_price,
                        created_at=datetime.now(),
                        updated_at=datetime.now()
                    )
                    db.add(transaction_item)
                    total_amount += item.unit_price

                # Update transaction total
                transaction.transaction_total_amount = total_amount
                return transaction

        except Exception as e:
            raise e

    async def create_sale_transaction(
        self,
        db: Session,
        transaction_data: SaleTransactionCreate
    ) -> Transaction:
        """
        this is basically psuedocode not implemented yet
        """
        try:
            with db_transaction(db):
                # Create the transaction
                transaction = Transaction(
                    customer_id=transaction_data.customer_id,
                    marketplace_id=transaction_data.marketplace_id,
                    transaction_type='sale',
                    transaction_date=transaction_data.transaction_date,
                    transaction_notes=transaction_data.transaction_notes,
                    created_at=datetime.now(),
                    updated_at=datetime.now()
                )
                db.add(transaction)
                db.flush()

                total_amount = 0
                for item in transaction_data.items:
                    # Get the inventory item and validate
                    inventory_item = db.query(InventoryItem).filter(
                        InventoryItem.id == item.inventory_item_id,
                        InventoryItem.deleted_at.is_(None)
                    ).first()
                    
                    if not inventory_item:
                        raise ValueError(f"Inventory item {item.inventory_item_id} not found")
                    
                    # Create transaction item
                    transaction_item = TransactionItem(
                        transaction_id=transaction.id,
                        physical_item_id=inventory_item.physical_item_id,
                        unit_price=item.unit_price,
                        created_at=datetime.now(),
                        updated_at=datetime.now()
                    )
                    db.add(transaction_item)
                    total_amount += item.unit_price

                    # Update marketplace listing if applicable
                    if transaction_data.marketplace_id and inventory_item.marketplace_listings:
                        listing = inventory_item.marketplace_listings
                        listing.delisting_date = transaction_data.transaction_date
                        listing.updated_at = datetime.now()

                # Update transaction total
                transaction.transaction_total_amount = total_amount
                return transaction

        except Exception as e:
            raise e
        
    async def create_customer(
        self,
        db: Session,
        customer_name: str
    ) -> Customer:
        try:
            # check if customer already exists
            existing_customer = db.query(Customer).filter(Customer.name == customer_name).first()
            if existing_customer:
                return existing_customer

            with db_transaction(db):
                customer = Customer(
                    name=customer_name,
                    created_at=datetime.now(),
                    updated_at=datetime.now()
                )
                db.add(customer)
                db.flush()
                return customer

        except Exception as e:
            raise e

    async def create_vendor(
        self,
        db: Session,
        vendor_name: str
    ) -> Vendor:
        try:
            # check if vendor already exists
            existing_vendor = db.query(Vendor).filter(Vendor.name == vendor_name).first()
            if existing_vendor:
                return existing_vendor

            with db_transaction(db):
                vendor = Vendor(
                    name=vendor_name,
                    created_at=datetime.now(),
                    updated_at=datetime.now()
                )
                db.add(vendor)
                db.flush()
                return vendor

        except Exception as e:
            raise e
        
    async def create_marketplace(
        self,
        db: Session,
        marketplace_name: str
    ) -> Marketplace:
        try:
            # check if marketplace already exists
            existing_marketplace = db.query(Marketplace).filter(Marketplace.name == marketplace_name).first()
            if existing_marketplace:
                return existing_marketplace

            with db_transaction(db):
                marketplace = Marketplace(
                    name=marketplace_name,
                    created_at=datetime.now(),
                    updated_at=datetime.now()
                )
                db.add(marketplace)
                db.flush()
                return marketplace

        except Exception as e:
            raise e

class SealedBoxService(BaseService[SealedBox]):
    def __init__(self):
        super().__init__(SealedBox)

    async def create_sealed_box(
        self,
        db: Session,
        product_id: int,
        cost_basis: float,
        case_id: Optional[int] = None
    ) -> SealedBox:
        try:
            with db_transaction(db):
                # Create the SealedBox
                sealed_box = SealedBox(
                    product_id=product_id,
                    created_at=datetime.now(),
                    updated_at=datetime.now()
                )
                db.add(sealed_box)
                db.flush()  # Get the ID for relationships

                # If this box is part of a case, link it
                if case_id:
                    case = db.query(SealedCase).filter(SealedCase.id == case_id).first()
                    if not case:
                        raise ValueError(f"Case {case_id} not found")
                    sealed_box.case_id = case_id

                # Create the InventoryItem for the sealed box
                inventory_item = InventoryItem(
                    physical_item=sealed_box,
                    cost_basis=cost_basis,
                    created_at=datetime.now(),
                    updated_at=datetime.now()
                )
                db.add(inventory_item)

                return sealed_box

        except Exception as e:
            raise e

class SealedCaseService(BaseService[SealedCase]):
    def __init__(self):
        super().__init__(SealedCase)

    async def create_sealed_case(self, db: Session, product_id: int, cost_basis: float, num_boxes: int) -> SealedCase:
        try:
            with db_transaction(db):
                # Create the SealedCase
                sealed_case = SealedCase(
                    product_id=product_id,
                    num_boxes=num_boxes,
                    created_at=datetime.now(),
                    updated_at=datetime.now()
                )
                db.add(sealed_case)
                db.flush()  # Get the ID for relationships

                # Create the InventoryItem for the sealed case
                inventory_item = InventoryItem(
                    physical_item=sealed_case,
                    cost_basis=cost_basis,
                    created_at=datetime.now(),
                    updated_at=datetime.now()
                )
                db.add(inventory_item)

                return sealed_case

        except Exception as e:
            raise e

    async def open_sealed_case(self, db: Session, sealed_case: SealedCase) -> bool:
        try:
            sealed_case_context = InventoryItemContextFactory(db).get_context(sealed_case.inventory_item)
            with db_transaction(db):
                # Create the OpenEvent
                open_event = OpenEvent(
                    sealed_case_id=sealed_case_context.physical_item.id,
                    open_date=datetime.now(),
                    created_at=datetime.now(),
                    updated_at=datetime.now()
                )
                db.add(open_event)
                db.flush()  # Get the ID for relationships

                # Create num_boxes SealedBoxes
                for i in range(sealed_case.num_boxes):
                    sealed_box = SealedBox(
                        product_id=sealed_case_context.physical_item.product_id,
                        created_at=datetime.now(),
                        updated_at=datetime.now()
                    )
                    db.add(sealed_box)
                    db.flush()  # Get the ID for relationships

                    # Create the InventoryItem for the sealed box
                    inventory_item = InventoryItem(
                        physical_item=sealed_box,
                        cost_basis=sealed_case_context.cost_basis,
                        created_at=datetime.now(),
                        updated_at=datetime.now()
                    )
                    db.add(inventory_item)

                return True

        except Exception as e:
            raise e
