from datetime import datetime

import pydash
import pytz

from configs.firebase import ArrayUnion, db
from V2.functions.Address.main import Address
from V2.functions.BlankProducts.main import BlankProduct, BlankVariant
from V2.functions.Images.main import Image
from V2.functions.Orders.main import Order
from V2.functions.Orders.OrderItem import OrderItem
from V2.functions.Printify.Auth import printifyTime
from V2.functions.Printify.Params import Location
from V2.functions.Printify.Params import PrintifyParams as Params
from V2.functions.Products.Variants import Property, Variant, VariantMapping
from V2.functions.Users.main import User
from V2.middlewares.auth import PrintifyError

PRINTIFY_PLACEMENT = {
    "front":"1",
    "back":"2",
    "right_sleeve": "6",
    "left_sleeve": "7"
}


PRINTIFY_SKUS = {
    "300-WHI-SMA":"3001-White-S",
    "300-WHI-MED":"3001-White-M",
    "300-WHI-LAR":"3001-White-L",
    "300-WHI-XLA":"3001-White-XL",
    "300-WHI-XXL":"3001-White-2XL",
    "300-WHI-XXX":"3001-White-3XL",
    "300-BLA-SMA":"3001-Black-S",
    "300-BLA-MED":"3001-Black-M",
    "300-BLA-LAR":"3001-Black-L",
    "300-BLA-XLA":"3001-Black-XL",
    "300-BLA-XXL":"3001-Black-2XL",
    "300-BLA-XXX":"3001-Black-3XL",
    "500-BLA-SMA":"5000-Black-S",
    "500-BLA-MED":"5000-Black-M",
    "500-BLA-LAR":"5000-Black-L",
    "500-BLA-XLA":"5000-Black-XL",
    "500-BLA-XXL":"5000-Black-2XL",
    "500-BLA-XXX":"5000-Black-3XL",
    "500-WHI-SMA":"5000-White-S",
    "500-WHI-MED":"5000-White-M",
    "500-WHI-LAR":"5000-White-L",
    "500-WHI-XLA":"5000-White-XL",
    "500-WHI-XXL":"5000-White-2XL",
    "500-WHI-XXX":"5000-White-3XL",
    "BLC-3001-WHITE-S": "3001-White-S",
    "BLC-3001-WHITE-M": "3001-White-M",
    "BLC-3001-WHITE-L": "3001-White-L",
    "BLC-3001-WHITE-XL": "3001-White-XL",
    "BLC-3001-WHITE-2XL":"3001-White-2XL",
    "BLC-3001-WHITE-3XL":"3001-White-3XL",
    "BLC-3001-BLACK-S":"3001-Black-S",
    "BLC-3001-BLACK-M":"3001-Black-M",
    "BLC-3001-BLACK-L":"3001-Black-L",
    "BLC-3001-BLACK-XL":"3001-Black-XL",
    "BLC-3001-BLACK-2XL":"3001-Black-2XL",
    "BLC-3001-BLACK-3XL":"3001-Black-3XL",
    "BLC-3001-ATHEATHER-S":"3001CVC-Athletic Heather-S",
    "BLC-3001-ATHEATHER-M":"3001CVC-Athletic Heather-M",
    "BLC-3001-ATHEATHER-L":"3001CVC-Athletic Heather-L",
    "BLC-3001-ATHEATHER-XL":"3001CVC-Athletic Heather-XL",
    "BLC-3001-ATHEATHER-2XL":"3001CVC-Athletic Heather-2XL",
    "BLC-3001-ATHEATHER-3XL":"3001CVC-Athletic Heather-3XL",
    "BLC-3001-NAVY-S":"3001-Navy-S",
    "BLC-3001-NAVY-M":"3001-Navy-M",
    "BLC-3001-NAVY-L":"3001-Navy-L",
    "BLC-3001-NAVY-XL":"3001-Navy-XL",
    "BLC-3001-NAVY-2XL":"3001-Navy-2XL",
    "BLC-3001-NAVY-3XL":"3001-Navy-3XL",
    "BLC-3001-DAHEATHERGREY-S":"3001CVC-Dark Grey Heather-S",
    "BLC-3001-DAHEATHERGREY-M":"3001CVC-Dark Grey Heather-M",
    "BLC-3001-DAHEATHERGREY-L":"3001CVC-Dark Grey Heather-L",
    "BLC-3001-DAHEATHERGREY-XL":"3001CVC-Dark Grey Heather-XL",
    "BLC-3001-DAHEATHERGREY-2XL":"3001CVC-Dark Grey Heather-2XL",
    "BLC-3001-DAHEATHERGREY-3XL":"3001CVC-Dark Grey Heather-3XL",
    "GLD-5000-BLACK-S":"5000-Black-S",
    "GLD-5000-BLACK-M":"5000-Black-M",
    "GLD-5000-BLACK-L":"5000-Black-L",
    "GLD-5000-BLACK-XL":"5000-Black-XL",
    "GLD-5000-BLACK-2XL":"5000-Black-2XL",
    "GLD-5000-BLACK-3XL":"5000-Black-3XL",
    "GLD-5000-WHITE-S":"5000-White-S",
    "GLD-5000-WHITE-M":"5000-White-M",
    "GLD-5000-WHITE-L":"5000-White-L",
    "GLD-5000-WHITE-XL":"5000-White-XL",
    "GLD-5000-WHITE-2XL":"5000-White-2XL",
    "GLD-5000-WHITE-3XL":"5000-White-3XL",
    "GLD-5000-NAVY-S":"5000-Navy-S",
    "GLD-5000-NAVY-M":"5000-Navy-M",
    "GLD-5000-NAVY-L":"5000-Navy-L",
    "GLD-5000-NAVY-XL":"5000-Navy-XL",
    "GLD-5000-NAVY-2XL":"5000-Navy-2XL",
    "GLD-5000-NAVY-3XL":"5000-Navy-3XL",
    "GLD-5000-SPGREY-S":"5000-Sport Grey-S",
    "GLD-5000-SPGREY-M":"5000-Sport Grey-M",
    "GLD-5000-SPGREY-L":"5000-Sport Grey-L",
    "GLD-5000-SPGREY-XL":"5000-Sport Grey-XL",
    "GLD-5000-SPGREY-2XL":"5000-Sport Grey-2XL",
    "GLD-5000-SPGREY-3XL":"5000-Sport Grey-3XL"
}

def getLocation(enterpriseId:str, locationId:str) -> dict | None:
    ref = db.document(f"enterprises/{enterpriseId}/locations/{locationId}").get()
    return ref.to_dict()


def getBlankProductFromSku(blankId:str, color:str, size:str) -> tuple[BlankProduct,BlankVariant]:
    blankQuery = db.collection("blankProducts").where("blankProductId", "==", blankId).where("public", "==", True).where("supplierId", "==", "SS").get()
    if len(blankQuery) > 0:
        blank = blankQuery[0]
        colorsQuery = [doc.id for doc in blank.reference.collection("blankVariants").where(
                "color", "in", [color, color.lower(), color.upper(), color.capitalize()]
            ).get()]
        sizesQuery  = [doc.id for doc in blank.reference.collection("blankVariants").where(
                "size", "in", [size, size.lower(), size.upper(), size.capitalize()]
            ).get()]
        variantQuery = [id for id in colorsQuery if id in sizesQuery]
        if len(variantQuery) > 0: 
            variantDoc = blank.reference.collection("blankVariants").document(variantQuery[0]).get()
            return BlankProduct.from_dict(blank.to_dict()), BlankVariant.from_dict(variantDoc.to_dict())
    return None, None


def savePrintifyOrder(printifyOrder:dict) -> str:
    ref = db.document(f"PrintifyOrders/{printifyOrder.get('id')}")
    ref.set(printifyOrder)
    return ref.id

def checkAddress(address:dict, type="address_to"):
    if not address: return False, "address"
    first_name, last_name = address.get("first_name"),address.get("last_name")
    address1 = address.get("address1")
    city, state, zipCode = address.get("city"), address.get("region"), address.get("zip")
    country = address.get("country")
    for name, field in {
        f"{type}.address1":address1,
        f"{type}.first_name": first_name,
        f"{type}.last_name": last_name,
        f"{type}.city": city,
        f"{type}.state": state,
        f"{type}.zip": zipCode,
        f"{type}.country": country,

    }.items():
        if not field:
            if name in ["address_from.first_name", "address_from.last_name"]:continue
            return False, name
    return True, None

def submitProductionOrder(params:Params):
    user = params.currentUser
    order = params.args
    updating:bool = order.get("updating")
    status_code = 200
    if params.id and not updating:
        status_code = 201
        location = Location.get(user.enterpriseId, params.id)
    else: location = params.location
    if updating: 
        del order['updating']
        location = params.location
    timezone = pytz.utc
    if location: timezone=pytz.timezone(location.timezone)
    if not location: raise PrintifyError(status_code=404,errors=[
            {
                "type": "facility_id",
                "message": f"Specified facility {params.args.get('locationId')} is invalid"
            }
    ])
    query = db.document(f"PrintifyOrders/{order.get('id')}").get()
    if location and updating: 
        return dict(
        errors=[
            dict(
                type="other",
                message=f"Order with ID '{order.get('id')}' already exists"
            )
        ]
    ), 409
    if query.exists and not updating: return dict(
        status="failed",
        errors=[
            dict(
                type="other",
                message=f"Order with ID '{order.get('id')}' already exists"
            )
        ]
    ), 304
    if updating:
        orders = db.collection(f"orders").where("platformOrderId", "==", order.get("id")).where("platformId", "==", "11").get()
        if len(orders) > 0:
            riverrOrder = Order.from_dict(orders[0].to_dict())
            if riverrOrder.shipped: raise PrintifyError(status_code =  400, code="expired", message="Order has already been shipped.")
            if riverrOrder.cancelled: raise PrintifyError(status_code =  400, code="expired", message="Order has already been canceled.")
        order = {**query.to_dict(), **order}
    printifyOrderId = order.get("id")
    orderNumber = str(int(datetime.now().timestamp()))
    address_to = order.get("address_to")
    toAddressValid = checkAddress(address_to, "address_to")
    valid,field = toAddressValid
    if not valid:
        return {
                "status": "failed" if not (updating or params.id) else None,
                "errors" : [
                    {
                    "type": "address_to",
                    "message": "Invalid To Address."
                    }
                ]
            }, 422
    address_from = order.get("address_from")
    fromAddressValid = checkAddress(address_from, "address_from")
    valid,field = fromAddressValid
    if not valid:
        return {
                "status": "failed" if not (updating or params.id) else None,
                "errors" : [
                    {
                    "type": "address_from",
                    "message": "Invalid From Address."
                    }
                ]
            }, 422
    fromAddress = Address(
            address1=address_from.get("address1"),
            address2=address_from.get("address2"),
            city=address_from.get("city"),
            country=address_from.get("country"),
            countryCode=address_from.get("country"),
            name=f'{address_from.get("first_name")} {address_from.get("last_name")}' if address_from.get("first_name") and address_from.get("last_name") else address_from.get("company"),
            email=address_from.get("email"),
            phone=address_from.get("phone"),
            company=address_from.get("company"),
            zip=address_from.get("zip"),
            state=address_from.get("region"),
            uid=user.uid,
            enterpriseId=user.enterpriseId,
            createdAt=datetime.now(timezone),
            updatedAt=datetime.now(timezone),
            default=False,
        )
    fromAddressId = fromAddress.save()
    shipping = order.get("shipping")
    if not shipping:
        return dict(
        status= "failed" if not (updating or params.id) else None,
        errors=[dict(
            type="shipping", message="Invalid carrier & method combination"
        )]
    ), 422
    if shipping.get("carrier") not in ['USPS', "UPS", "DHL", "GSO"] or shipping.get("priority") not in ['Flat',
                    'Priority','Standard', "Express", "First", "CaliforniaParcelService", "Ground", "2ndDayAir", "NextDayAir"]:
        return dict(
                    status= "failed" if not (updating or params.id) else None,
                    errors=[
                        {
                        "type": "shipping",
                        "message": "Invalid carrier & method combination"
                    }
                ]), 422
    items:list[dict] = order.get("items", [])
    if not items: return {
            "status":"failed" if not updating else None,
            "errors" : [
                {
                "type": "items",
                "message": "No items in order."
                }
            ]
        },  400 if not updating else 422 
    tags = order.get("tags", [])
    # if not tags: 
    #     print(tags)
    #     raise PrintifyError(status_code = 422,
    #        errors=[dict(
    #             type="tags",
    #             message='Invalid Tags.'
    #        )]
    #     )
    package_inserts = order.get("package_inserts", [])
    convertedOrder = Order(
        uid=user.uid,
        enterpriseId=user.enterpriseId,
        createdAt=datetime.now(tz=timezone),
        updatedAt=datetime.now(tz=timezone),
        cancelled=False,
        currencyCode="USD",
        draft=False,
        grandTotal=len(items),
        platformOrderId=printifyOrderId,
        shipped=False,
        shippingCost=0,
        shopId="11"+location.id,
        platformId="11",
        shippingAddress=Address(
            address1=address_to.get("address1"),
            address2=address_to.get("address2"),
            city=address_to.get("city"),
            country=address_to.get("country"),
            countryCode=address_to.get("country"),
            name=f'{address_to.get("first_name")} {address_to.get("last_name")}',
            email=address_to.get("email"),
            phone=address_to.get("phone"),
            company=address_to.get("company"),
            zip=address_to.get("zip"),
            state=address_to.get("region"),
        ),
        metadata=dict(
            fromAddressId=fromAddressId,
            shipping=shipping,
            tags=tags,
            sample=order.get("sample"),
            location=location.to_dict(),
            events=[dict(
                time=printifyTime(timezone.zone),
                action="created",
                affected_items=[item.get('id') for item in items]
            )]
        ),
        totalDiscount=0,
        totalPrice=1,
        totalTax=0,
        shopName=f"Printify - {location.name}" if location else "Printify",
        labels=ArrayUnion([dict(id=None, url=m.get("url"), type="message") for m in package_inserts]) if len(package_inserts)>0 else [],
    )
    convertedOrderItems:list[OrderItem] = []
    for item in items:
        printImages = [
            Image(
                id=str(items.index(item)),
                name=placement,
                placement= PRINTIFY_PLACEMENT.get(placement),
                createdAt=datetime.now(timezone),
                updatedAt=datetime.now(timezone),
                enterpriseId=user.enterpriseId,
                uid=user.uid,
                url=url,
            ) for placement,url in item.get("print_files", []).items()
        ]
        sku = PRINTIFY_SKUS.get(item.get("sku", ''))
        if not sku: sku = item.get("sku", '')
        if not sku or "-" not in sku:
            raise PrintifyError(
            status="failed" if not updating else None, 
            status_code= 400 if not updating else 422 ,
            errors=[
                dict(
                    type="items",
                    message=f"Item with SKU {sku} cannot be fulfilled."
                )
            ]
        )
        blankId, color, size  = sku.split("-")
        blankProduct, blankVariant = getBlankProductFromSku(blankId, color, size)
        if not (blankProduct or blankVariant): raise PrintifyError(
            status="failed" if not updating else None, 
            status_code= 400 if not updating else 422 ,
            errors=[
                dict(
                    type="items",
                    message=f"Item with SKU {sku} cannot be fulfilled."
                )
            ]
        )
        purchaseOrderData = VariantMapping(
            blankProductId=blankProduct.id,
            blankVariantId=blankVariant.id,
            id=sku,
            createdAt=datetime.now(tz=timezone),
            updatedAt=datetime.now(tz=timezone),
            enterpriseId=user.enterpriseId,
            uid=user.enterpriseId,
            ignored=False,
            images=printImages,
            placements=[image.placement for image in printImages],
            productId=blankProduct.id,
            sku=sku,
        )
        orderItem = OrderItem(
                platformId="11",
                discount=0,
                batched=False,
                ignored=False,
                batchId=None, 
                cancelled=False,
                createdAt=datetime.now(tz=timezone),
                updatedAt=datetime.now(tz=timezone),
                draft=False,
                enterpriseId=user.enterpriseId,
                uid=user.uid,
                image=Image(url=item.get("preview_files", {}).get("front", item.get("preview_files", {}).get("back"))),
                images=[Image(
                    url=url,
                    name=name,
                    placement=PRINTIFY_PLACEMENT.get(name)
                    ) for name, url in item.get("preview_files", {}).items() if name in ['front', 'back']],
                quantity=item.get("quantity"),
                name=item.get("sku"),
                platformOrderItemId=item.get("id"),
                platformOrderId=printifyOrderId,
                price=0,
                purchaseOrderData=purchaseOrderData,
                variant=Variant(
                    id=sku,
                    sku=sku,
                    createdAt=datetime.now(tz=timezone),
                    updatedAt=datetime.now(tz=timezone),
                    enterpriseId=user.enterpriseId,
                    uid=user.uid,
                    name=sku,
                    price=1,
                    productId=blankProduct.id,
                    properties=[
                        Property(
                            name="Color",
                            value=color,
                        ),
                        Property(
                            name="Size",
                            value=size,
                        )
                    ],
                    deleted=False
                ),
                platformProductId=blankId,
                productId="11"+convertedOrder.shopId+blankId,
                shopId=convertedOrder.shopId,
                variantId=sku,
                subtotal=1,
                tax=0,
                total=1,
                shipped=False,
                printOnDemand=True,
        )
        convertedOrderItems.append(orderItem)
    orderId = convertedOrder.save(orderItems=convertedOrderItems, rewrite=True)
    order['reference_id'] = orderId
    savePrintifyOrder(order)
    return (dict(
        reference_id=orderId,
        status="success",
        id=convertedOrder.platformOrderId
    ), status_code) if not updating and not params.id else (dict(
        reference_id=orderId,
        status="success" if updating and not params.id else None,
    ), status_code)



def getPrintifyOrder(params: Params) -> dict:
    query = db.document(f"PrintifyOrders/{params.id}").get()
    if query.exists: return query.to_dict()
    raise PrintifyError(status_code = 404, status='Not found.')


def getPrintifyOrderEvents(params:Params) -> dict:
    docs = db.collection("orders").where("platformOrderId", '==', params.id).where("platformId", "==", "11").get()
    if len(docs) > 0: 
        order = Order.from_dict(docs[0].to_dict())
        return dict(
            status = order.metadata.get("status") if order.metadata.get("status") else ("shipped" if order.shipped else "created"),
            events = order.metadata.get("events", [])
        )
    raise PrintifyError(status_code = 404, errors=[dict(
        id=params.id,
        message="Order not found."
    )])

def findPrintifyOrder(printifyOrderId:str) -> Order|None:
    query = db.collection("orders").where("platformId", "==", "11").where("platformOrderId", "==", printifyOrderId).get()
    if len(query) > 0: return Order.from_dict(query[0].to_dict())
    return None

def updatePrintifyOrder(params: Params) -> dict:
    params.args['updating'] = True
    params.args['id'] = params.id
    params.id = None
    return submitProductionOrder(params)


def cancelPrintifyOrder(params:Params) -> tuple[str | dict,int]:
    printifyOrderId = params.id
    order = findPrintifyOrder(printifyOrderId)
    items = params.args.get("items", [])
    if not order: raise PrintifyError(status_code = 404, errors=[
            dict(
                id=printifyOrderId,
                message="Order not found."
            )
        ])
    if order.shipped: raise PrintifyError(status_code =  409, errors=[
            dict(
                id=printifyOrderId,
                message="The order is already shipped and cannot be canceled."
            )
        ])
    if order.cancelled: raise PrintifyError(status_code = 409, errors=[
            dict(
                id=printifyOrderId,
                message="The order is already canceled and cannot be canceled."
            )
        ])
    orderItems = order.getOrderItems()
    cancelledItems = []
    for orderItem in orderItems:
        if orderItem.cancelled: raise PrintifyError(status_code =  409, errors=[
                dict(
                    id=orderItem.platformOrderItemId,
                    message="Order line item is already cancelled and could not be canceled."
                )
            ])
        if orderItem.batched:
            raise PrintifyError(status_code =  409, errors=[
                dict(
                    id=orderItem.platformOrderItemId,
                    message="Order line item is already fulfilled and could not be canceled."
                )
            ])
        if orderItem.platformOrderItemId in items: cancelledItems.append(orderItem.id)
    if order:
        platformOrderItemIds = [item.platformOrderItemId for item in orderItems]
        for itemId in items:
            if itemId not in platformOrderItemIds:
                raise PrintifyError(status_code =  409, errors=[
                dict(
                    id=itemId,
                    message="Order line item not found."
                )
            ])
        order.cancel(orderItemIds=cancelledItems)
        return dict(status="success"), 204
    raise PrintifyError(status_code = 404, errors=[dict(
        id=printifyOrderId, 
        message="Order not found."
    )])




 