from typing import Any, Dict, Optional, Union, Literal
import logging
from app.services.external_api.tcgplayer.base_tcgplayer_service import BaseTCGPlayerService
from app.schemas.tcgplayer import (
    TCGPlayerAPIOrder,
    TCGPlayerOrderCreate,
    TCGPlayerOrderTransactionCreate,
    TCGPlayerOrderProductCreate,
    TCGPlayerOrderRefundCreate,
    TCGPlayerAPIOrderSummary,
    TCGPlayerAPIOrderSearchResponse
)
from app.models.tcgplayer_order import (
    TCGPlayerOrder,
    TCGPlayerOrderTransaction,
    TCGPlayerOrderProduct,
    TCGPlayerOrderRefund
)
from app.models.tcgplayer_products import TCGPlayerProduct
from sqlalchemy.orm import Session
from app.db.database import transaction
import os
import csv
import io
from app.schemas.file import FileInDB
from datetime import datetime
logger = logging.getLogger(__name__)

class OrderManagementService(BaseTCGPlayerService):
    ORDER_MANAGEMENT_BASE_URL = "https://order-management-api.tcgplayer.com/orders"
    
    def __init__(self):
        super().__init__()
        self.base_url = self.ORDER_MANAGEMENT_BASE_URL
        self.API_VERSION: str = "?api-version=2.0"
        self.SELLER_KEY: str = "e576ed4c"
        
        self.order_search_endpoint = f"/search{self.API_VERSION}"
        self.packing_slip_endpoint = f"/packing-slips/export{self.API_VERSION}"
        self.pull_sheet_endpoint = f"/pull-sheets/export{self.API_VERSION}"
        self.shipping_endpoint = f"/shipping/export{self.API_VERSION}"
    
    
    async def get_orders(self, search_range: str = "LastThreeMonths", open_only: bool = False, filter_out: list[str] = [], filter_in: list[str] = []) -> list[TCGPlayerAPIOrderSummary]:
        """
        search range options:
            LastWeek
            LastMonth
            LastThreeMonths
            LastFourMonths
            LastTwoYears
        """
        search_from = 0
        orders = []
        while True:
            payload = {
                "searchRange": search_range,
                "filters": {
                "sellerKey": self.SELLER_KEY
            },
            "sortBy": [
                {"sortingType": "orderStatus", "direction": "ascending"},
                {"sortingType": "orderDate", "direction": "descending"}
            ],
            "from": search_from,
                "size": 25
            }
            if open_only:
                payload["filters"]["orderStatuses"] = ["Processing","ReadyToShip","Received","Pulling","ReadyForPickup"]
                payload["filters"]["fulfillmentTypes"] = ["Normal"]
            logger.info(f"Getting orders from {search_from} to {search_from + 25}")
            response = await self._make_request("POST", self.order_search_endpoint, data=payload, headers=self._get_headers("POST", "application/json"), auth_required=True)
            logger.info(f"Got {len(response.get('orders'))} orders")
            if len(response.get("orders")) == 0:
                break
            search_from += 25
            orders.extend(response.get("orders"))
            if filter_out:
                orders = [order for order in orders if order.get("orderNumber") not in filter_out]
            if filter_in:
                orders = [order for order in orders if order.get("orderNumber") in filter_in]
        return orders
    
    async def get_order_ids(self, search_range: str = "LastThreeMonths", open_only: bool = False, filter_out: list[str] = [], filter_in: list[str] = []):
        orders = await self.get_orders(search_range, open_only, filter_out, filter_in)
        return [order.get("orderNumber") for order in orders]
    
    async def get_order(self, order_id: str):
        response = await self._make_request("GET", f"/{order_id}{self.API_VERSION}")
        return response
    
    async def get_or_create_packing_slip(self, db: Session, order_ids: list[str]) -> FileInDB:
        # check if the file already exists
        file_service = self.get_service('file')
        file = await file_service.get_file_by_metadata(db, "order_ids", order_ids, "packing_slip", "application/pdf")
        if file:
            return file
        payload = {
            "sortingType": "byRelease",
            "format": "default",
            "timezoneOffset": -4,
            "orderNumbers": order_ids
        }
        response = await self._make_request("POST", self.packing_slip_endpoint, data=payload, headers=self._get_headers("POST", "application/json"), auth_required=True, download_file=True)
        return await file_service.save_file(
            db=db,
            file_data=response,
            filename=f"packing_slip_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.pdf",
            subdir='tcgplayer/packing_slips/pdf',
            file_type='packing_slip',
            content_type='application/pdf',
            metadata={"order_ids": order_ids}
        )
    
    async def get_pull_sheet(self, db: Session, order_ids: list[str]) -> FileInDB:
        payload = {
            "orderNumbers": order_ids,
            "timezoneOffset": -4
        }
        response = await self._make_request("POST", self.pull_sheet_endpoint, data=payload, headers=self._get_headers("POST", "application/json"), auth_required=True, download_file=True)
        # get file service
        file_service = self.get_service('file')
        # save file
        return await file_service.save_file(
            db=db,
            file_data=response,
            filename=f"pull_sheet_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.csv",
            subdir='tcgplayer/pull_sheets/csv',
            file_type='pull_sheet',
            content_type='text/csv',
            metadata={"order_ids": order_ids}
        )
    
    async def get_shipping_csv(self, db: Session, order_ids: list[str]) -> FileInDB:
        payload = {
            "orderNumbers": order_ids,
            "timezoneOffset": -4
        }
        response = await self._make_request("POST", self.shipping_endpoint, data=payload, headers=self._get_headers("POST", "application/json"), auth_required=True, download_file=True)
        # get file service
        file_service = self.get_service('file')
        # save file
        return await file_service.save_file(
            db=db,
            file_data=response,
            filename=f"shipping_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.csv",
            subdir='tcgplayer/shipping/csv',
            file_type='shipping_csv',
            content_type='text/csv',
            metadata={"order_ids": order_ids}
        )
        
    async def save_order_to_db(self, order: dict, db: Session):
        # Parse API response using our API schema
        api_order = TCGPlayerAPIOrder.model_validate(order)
        
        # Check if order exists
        existing_order = db.query(TCGPlayerOrder).filter(TCGPlayerOrder.order_number == api_order.orderNumber).first()
        
        # Create database models
        db_order = TCGPlayerOrderCreate(
            order_number=api_order.orderNumber,
            order_created_at=api_order.createdAt,
            status=api_order.status,
            channel=api_order.orderChannel,
            fulfillment=api_order.orderFulfillment,
            seller_name=api_order.sellerName,
            buyer_name=api_order.buyerName,
            payment_type=api_order.paymentType,
            pickup_status=api_order.pickupStatus,
            shipping_type=api_order.shippingType,
            estimated_delivery_date=api_order.estimatedDeliveryDate,
            recipient_name=api_order.shippingAddress.recipientName,
            address_line_1=api_order.shippingAddress.addressOne,
            address_line_2=api_order.shippingAddress.addressTwo,
            city=api_order.shippingAddress.city,
            state=api_order.shippingAddress.territory,
            zip_code=api_order.shippingAddress.postalCode,
            country=api_order.shippingAddress.country,
            tracking_numbers=[t.trackingNumber for t in api_order.trackingNumbers]
        )
        
        # Create transaction
        db_transaction = TCGPlayerOrderTransactionCreate(
            order_number=api_order.orderNumber,
            product_amount=api_order.transaction.productAmount,
            shipping_amount=api_order.transaction.shippingAmount,
            gross_amount=api_order.transaction.grossAmount,
            fee_amount=api_order.transaction.feeAmount,
            net_amount=api_order.transaction.netAmount,
            direct_fee_amount=api_order.transaction.directFeeAmount,
            taxes=[{"code": t.code, "amount": t.amount} for t in api_order.transaction.taxes]
        )
                # Create products
        db_products = [
            TCGPlayerOrderProductCreate(
                order_number=api_order.orderNumber,
                product_name=p.name,
                unit_price=p.unitPrice,
                extended_price=p.extendedPrice,
                quantity=p.quantity,
                url=p.url,
                product_id=p.productId,
                sku_id=p.skuId
            )
            for p in api_order.products
        ]
        
        # Create refunds if they exist
        db_refunds = []
        if api_order.refunds:
            db_refunds = [
                TCGPlayerOrderRefundCreate(
                    order_number=api_order.orderNumber,
                    refund_created_at=r.createdAt,
                    type=r.type,
                    amount=r.amount,
                    description=r.description,
                    origin=r.origin,
                    shipping_amount=r.shippingAmount,
                    products=r.products
                )
                for r in api_order.refunds
            ]
        
        with transaction(db):
            try:
                if existing_order:
                    # Check if order needs to be updated
                    if not self._order_needs_update(existing_order, api_order, db):
                        logger.info(f"Order {api_order.orderNumber} is up to date, skipping update")
                        return existing_order
                    
                    logger.info(f"Updating existing order {api_order.orderNumber}")
                    # Update existing order
                    for key, value in db_order.model_dump().items():
                        setattr(existing_order, key, value)
                    db_order_model = existing_order
                    
                    # Update transaction
                    existing_transaction = db.query(TCGPlayerOrderTransaction).filter(
                        TCGPlayerOrderTransaction.order_number == api_order.orderNumber
                    ).first()
                    if existing_transaction:
                        for key, value in db_transaction.model_dump().items():
                            setattr(existing_transaction, key, value)
                    else:
                        db_transaction_model = TCGPlayerOrderTransaction(**db_transaction.model_dump())
                        db.add(db_transaction_model)
                    
                    # Delete existing products and refunds
                    db.query(TCGPlayerOrderProduct).filter(
                        TCGPlayerOrderProduct.order_number == api_order.orderNumber
                    ).delete()
                    db.query(TCGPlayerOrderRefund).filter(
                        TCGPlayerOrderRefund.order_number == api_order.orderNumber
                    ).delete()
                else:
                    logger.info(f"Creating new order {api_order.orderNumber}")
                    # Create new order
                    db_order_model = TCGPlayerOrder(**db_order.model_dump())
                    db.add(db_order_model)
                    db.flush()  # Get the order ID
                    
                    # Create transaction
                    db_transaction_model = TCGPlayerOrderTransaction(**db_transaction.model_dump())
                    db.add(db_transaction_model)
                
                # Create products
                for product in db_products:
                    db_product_model = TCGPlayerOrderProduct(**product.model_dump())
                    db.add(db_product_model)
                
                # Create refunds
                for refund in db_refunds:
                    db_refund_model = TCGPlayerOrderRefund(**refund.model_dump())
                    db.add(db_refund_model)
                
                db.commit()
                return db_order_model
                
            except Exception as e:
                logger.error(f"Error saving/updating order {api_order.orderNumber} to database: {str(e)}")
                raise

    def _order_needs_update(self, existing_order: TCGPlayerOrder, new_order: TCGPlayerAPIOrder, db: Session) -> bool:
        """
        Compare existing order data with new order data to determine if an update is needed.
        Returns True if the order needs to be updated, False otherwise.
        """
        # Compare basic order fields
        order_fields_to_compare = [
            ('status', 'status'),
            ('channel', 'orderChannel'),
            ('fulfillment', 'orderFulfillment'),
            ('seller_name', 'sellerName'),
            ('buyer_name', 'buyerName'),
            ('payment_type', 'paymentType'),
            ('pickup_status', 'pickupStatus'),
            ('shipping_type', 'shippingType'),
            ('recipient_name', 'shippingAddress.recipientName'),
            ('address_line_1', 'shippingAddress.addressOne'),
            ('address_line_2', 'shippingAddress.addressTwo'),
            ('city', 'shippingAddress.city'),
            ('state', 'shippingAddress.territory'),
            ('zip_code', 'shippingAddress.postalCode'),
            ('country', 'shippingAddress.country'),
            ('tracking_numbers', 'trackingNumbers')
        ]
        
        for db_field, api_field in order_fields_to_compare:
            existing_value = getattr(existing_order, db_field)
            
            # Handle nested fields
            if '.' in api_field:
                parts = api_field.split('.')
                new_value = new_order
                for part in parts:
                    new_value = getattr(new_value, part)
            else:
                new_value = getattr(new_order, api_field)
            
            # Handle special cases for tracking numbers
            if db_field == 'tracking_numbers':
                if set(existing_value or []) != set([t.trackingNumber for t in new_value or []]):
                    return True
                continue
                
            if existing_value != new_value:
                return True
        
        # Compare transaction data
        existing_transaction = db.query(TCGPlayerOrderTransaction).filter(
            TCGPlayerOrderTransaction.order_number == existing_order.order_number
        ).first()
        
        if existing_transaction:
            transaction_fields_to_compare = [
                ('product_amount', 'productAmount'),
                ('shipping_amount', 'shippingAmount'),
                ('gross_amount', 'grossAmount'),
                ('fee_amount', 'feeAmount'),
                ('net_amount', 'netAmount'),
                ('direct_fee_amount', 'directFeeAmount')
            ]
            
            for db_field, api_field in transaction_fields_to_compare:
                if getattr(existing_transaction, db_field) != getattr(new_order.transaction, api_field):
                    return True
            
            # Compare taxes
            existing_taxes = sorted(existing_transaction.taxes, key=lambda x: x['code'])
            new_taxes = sorted(new_order.transaction.taxes, key=lambda x: x.code)
            if len(existing_taxes) != len(new_taxes):
                return True
            for existing_tax, new_tax in zip(existing_taxes, new_taxes):
                if existing_tax['code'] != new_tax.code or existing_tax['amount'] != new_tax.amount:
                    return True
        
        # Compare products
        existing_products = db.query(TCGPlayerOrderProduct).filter(
            TCGPlayerOrderProduct.order_number == existing_order.order_number
        ).all()
        
        if len(existing_products) != len(new_order.products):
            return True
            
        # Sort products by product_id for comparison
        existing_products_sorted = sorted(existing_products, key=lambda x: x.product_id)
        new_products_sorted = sorted(new_order.products, key=lambda x: x.productId)
        
        for existing_product, new_product in zip(existing_products_sorted, new_products_sorted):
            product_fields_to_compare = [
                ('product_name', 'name'),
                ('unit_price', 'unitPrice'),
                ('extended_price', 'extendedPrice'),
                ('quantity', 'quantity'),
                ('url', 'url'),
                ('tcgplayer_product_id', 'productId'),
                ('tcgplayer_sku_id', 'skuId')
            ]
            
            for db_field, api_field in product_fields_to_compare:
                if getattr(existing_product, db_field) != getattr(new_product, api_field):
                    return True
        
        # Compare refunds
        existing_refunds = db.query(TCGPlayerOrderRefund).filter(
            TCGPlayerOrderRefund.order_number == existing_order.order_number
        ).all()
        
        if len(existing_refunds) != len(new_order.refunds or []):
            return True
            
        # Sort refunds by created_at for comparison
        existing_refunds_sorted = sorted(existing_refunds, key=lambda x: x.refund_created_at)
        new_refunds_sorted = sorted(new_order.refunds or [], key=lambda x: x.createdAt)
        
        for existing_refund, new_refund in zip(existing_refunds_sorted, new_refunds_sorted):
            refund_fields_to_compare = [
                ('type', 'type'),
                ('amount', 'amount'),
                ('description', 'description'),
                ('origin', 'origin'),
                ('shipping_amount', 'shippingAmount'),
                ('products', 'products')
            ]
            
            for db_field, api_field in refund_fields_to_compare:
                if getattr(existing_refund, db_field) != getattr(new_refund, api_field):
                    return True
        
        return False
        