from dataclasses import dataclass, field

import requests

from configs.firebase import db
from functions.Shipstation.Auth import Authorization
from V2.functions.Address.main import Address
from V2.functions.Applications.main import Application
from V2.functions.BlankProducts.main import BlankProduct, BlankVariant
from V2.functions.main import DocumentData, validate
from V2.functions.Products.Variants import VariantMapping
from V2.functions.Shops.main import Shop
from V2.middlewares.auth import API_Error
from V2.Params import Params


def getShippingSpeed(enterpriseId:str, shippingSpeedId:str) -> dict | None:
    doc = db.document(f"enterprises/{enterpriseId}/shippingSpeeds/{shippingSpeedId}").get()
    if doc.exists: return doc.to_dict()
    return None


def getGlobalShippingSpeed(id:str) -> dict | None:
    doc = db.document(f"shippingSpeeds/{id}").get()
    if doc.exists: return doc.to_dict()
    return None


@dataclass(kw_only=True)
class Shipment(DocumentData):
    orderId:str
    platformOrderId:str
    platformId:str
    platformName:str
    platformShipmentId:str
    trackingCode:str
    trackingUrl:str
    carrierName:str
    carrierService:str
    cost:float
    image:str
    pdf:str
    userUid:str
    currency:str = "USD"
    routedOrderIds:list[str] = field(default_factory=list)

    def __post_init__(self):
        validate(self)

    def save(self, routedOrderIds:list[str] = []):
        _, ref = db.collection("shipments").add(self.to_dict())
        ref.update(dict(id=ref.id))
        self.id = ref.id
        shipment = self.to_dict()
        if not routedOrderIds: routedOrderIds = []
        for id in routedOrderIds:
            shipment['orderId'] = id
            shipment['id'] = ref.id+"-"+id
            from V2.functions.Orders.main import Order
            thisOrder = Order.get(id)
            if thisOrder:
                shipment['userId'] = thisOrder.uid 
                shipment['enterpriseId'] = thisOrder.enterpriseId
            db.collection("shipments").document(shipment.get("id")).set(shipment)
        return shipment


def shipNow(params:Params):
    user = params.currentUser
    uid, enterpriseId = user.uid, user.enterpriseId
    orderId = params.args.get("orderId")
    app = params.args.get("app", Application.get("EASYPOST"+user.enterpriseId, type="shipping"))
    from V2.functions.Orders.main import Order
    order = Order.get(orderId)
    if order:
        if order.shipped: return
        metadata = order.metadata
        shipping = None
        fromAddressId = 'GglHeQqlMKasLA3upoQc' if enterpriseId == "60D7GFDlMFFd6IsK1e58" else None
        if metadata: 
            shippingLabel = order.metadata.get("shippingLabel")
            carrierName = order.metadata.get("carrierName")
            carrierService = order.metadata.get("carrierService")
            trackingCode = order.metadata.get("trackingCode")
            if shippingLabel:
                shipment = Shipment(
                    uid=uid,
                    enterpriseId=enterpriseId,
                    orderId=order.id,
                    platformOrderId=order.platformOrderId,
                    platformId="Manual",
                    platformName="Manual",
                    platformShipmentId=order.platformOrderId,
                    trackingCode=trackingCode,
                    trackingUrl=None, 
                    carrierName=carrierName,
                    carrierService=carrierService,
                    cost=0,
                    image=shippingLabel if not isPdf(shippingLabel) else None,
                    userUid=order.uid,
                    routedOrderIds=order.routedOrderIds,
                    pdf=shippingLabel if isPdf(shippingLabel) else None
                )
                shipment.save(routedOrderIds=order.routedOrderIds if order.routedOrderIds else [])
                import time
                time.sleep(1)
                update = order.markAsShipped(shipment=shipment)
                return dict(**shipment.to_dict(), **update)
            shipping = order.metadata.get("shipping", {})
        # if not (order.shippingSpeedId or shipping): return shipNowSmartShipment(params)
        fromAddress = Address.get(fromAddressId) if fromAddressId else Address.getDefaultAddress(enterpriseId , uid=uid)
        if not fromAddress: raise API_Error("No from address selected, Please set default from address.", 400)
        orderItems = order.getOrderItems()
        blankVariantIds = set((VariantMapping.from_dict(orderItem.purchaseOrderData).blankProductId,VariantMapping.from_dict(orderItem.purchaseOrderData).blankVariantId, orderItem.quantity) for orderItem in orderItems if not orderItem.ignored)
        blankVariants = [(BlankVariant.get(blankProductId,blankVariantId), quantity) for blankProductId,blankVariantId, quantity in blankVariantIds]
        totalWeight = sum(blankVariant.weight*quantity for blankVariant, quantity in blankVariants)
        if enterpriseId == "gSo3G298DNypOMvofnaR":totalWeight = 4*sum(quantity for _, quantity in blankVariants)
        parcel = dict(
            height=params.args.get('height',2),
            weight = totalWeight if totalWeight>0 else 5,
            length = params.args.get('length',12),
            width = params.args.get('width',15),
        )
        if order.shopName and order.enterpriseId != "60D7GFDlMFFd6IsK1e58": fromAddress.name = order.shopName
        if shipping:
            carrierId, serviceId = shipping.get("carrier"), shipping.get("priority")
            returnAddress = Address.get(metadata.get("fromAddressId"))
            if carrierId in ["GSO", "GLS"]: fromAddress.name = returnAddress.name
            if serviceId == "Flat":
                parcel = dict(
                    weight=shipping.get("weight", 6),
                    predefined_package="Flat"
                )
                serviceId = "First"
            from V2.functions.Shipments.EasyPost import shipNowEasypost
            shipment = shipNowEasypost(user,app,fromAddress, order, parcel, carrierId, serviceId, returnAddress=returnAddress)
            shipment.save(routedOrderIds=order.routedOrderIds if order.routedOrderIds else [])
            update = order.markAsShipped(shipment=shipment)
            return dict(**shipment.to_dict(), **update)
        platformId = "EASYPOST"
        packageId = "package"
        if order.shippingAddress.get("country") not in ["United States", "USA", "US", "United States of America"]: order.shippingSpeedId = "International"
        if order.shippingSpeedId: 
            shippingSpeed = getShippingSpeed(enterpriseId, order.shippingSpeedId)
            if not shippingSpeed: raise API_Error("Shipping speed not found or not set.")
            globalShippingSpeed = getGlobalShippingSpeed(shippingSpeed.get('carrierId', ''))
            if not globalShippingSpeed: raise API_Error("Shipping speed not found or not set.")
            carrierId, serviceId = globalShippingSpeed.get('platformCarrierId'), next((service.get("id") for service in globalShippingSpeed.get('services', []) if service.get("id") == shippingSpeed.get("serviceId")), None)
            platformId = globalShippingSpeed.get('platformId')
            packageId = shippingSpeed.get("packageId")
        else:
            if totalWeight < 1: shippingSpeed = getShippingSpeed(enterpriseId, "<1oz")
            else: shippingSpeed = getShippingSpeed(enterpriseId, "<1lb" if totalWeight < 16 else ">1lb")
            if not shippingSpeed: raise API_Error("Shipping speed not found or not set.")
            globalShippingSpeed = getGlobalShippingSpeed(shippingSpeed.get('carrierId', ''))
            if not globalShippingSpeed: raise API_Error("Shipping speed not found or not set.")
            carrierId, serviceId = globalShippingSpeed.get('platformCarrierId'), next((service.get("id") for service in globalShippingSpeed.get('services', []) if service.get("id") == shippingSpeed.get("serviceId")), None)
            platformId = globalShippingSpeed.get('platformId')
            packageId = shippingSpeed.get("packageId")
        parcel = dict(
            height=params.args.get('height',2),
            weight = totalWeight if totalWeight>0 else 5,
            length = params.args.get('length',12),
            width = params.args.get('width',15),
        )
        returnAddress = None
        if order.uid == "LNU7wWw9cSMHeMcAavdeMynapk82":
            returnAddress = Address(
                    name="The Print Bar Apparel",
                    address1 = "PO Box 153",
                    address2 = "",
                    city = "Mason",
                    state = "OH",
                    zip = "45040",
                    country = "United States",
                    phone = "1111111111", 
                )
        else: returnAddress = Address.from_dict(fromAddress.to_dict())
        from V2.functions.Shipments.EasyPost import shipNowEasypost
        if platformId == "EASYPOST": shipment = shipNowEasypost(user,app,fromAddress, order, parcel, carrierId, serviceId, returnAddress=returnAddress)
        else:
            app = Application.get(platformId+enterpriseId, type="shipping")
            from V2.functions.Shipments.Shipstation import shipNowShipstation
            shipment = shipNowShipstation(
                uid,
                order,
                app,
                fromAddress,
                carrierId=carrierId,
                serviceId=serviceId,
                parcel=parcel,
                packageId=packageId,
            )
        shipment.save(routedOrderIds=order.routedOrderIds if order.routedOrderIds else [])
        update = order.markAsShipped(shipment=shipment)
        from functions.Shipments.Main import submitShipmentToPlatform
        submitShipmentToPlatform(order.platformId, order.shopId, order.platformOrderId, shipment.trackingCode, shipment.carrierName,carrierService=shipment.carrierService, order=order.to_dict())
        return dict(**shipment.to_dict(), **update)
    
def isPdf(url):
    return url[-3:] == "pdf"

def shipNowSmartShipment(params:Params):
    user = params.currentUser
    orderId = params.args.get("orderId")
    from V2.functions.Orders.main import Order
    order = Order.get(orderId)
    app = params.args.get("app", Application.get("EASYPOST"+user.enterpriseId, type="shipping"))
    if order:
        orderItems = order.getOrderItems()
        blankVariantIds = set((VariantMapping.from_dict(orderItem.purchaseOrderData).blankProductId,VariantMapping.from_dict(orderItem.purchaseOrderData).blankVariantId) for orderItem in orderItems)
        blankVariants = [BlankVariant.get(blankProductId,blankVariantId) for blankProductId,blankVariantId in blankVariantIds]
        totalWeight = sum(blankVariant.weight for blankVariant in blankVariants)
        fromAddress = Address.getDefaultAddress(user.enterpriseId, uid=user.uid)
        if not fromAddress: raise API_Error("No from address selected, Please set default from address.", 400)
        parcel = dict(
            height=params.args.get('height',2),
            weight = totalWeight if totalWeight>0 else 5,
            length = params.args.get('length',12),
            width = params.args.get('width',15),
        )
        from V2.functions.Shipments.EasyPost import shipNowEasypost
        shipment = shipNowEasypost(user, app, fromAddress, order, parcel)
        from functions.Shipments.Main import submitShipmentToPlatform
        if not order.get("copied"): submitShipmentToPlatform(order.platformId, order.shopId, order.platformOrderId, shipment.trackingCode, shipment.carrierName,carrierService=shipment.carrierService, order=order.to_dict())
        shipment.save(routedOrderIds=order.routedOrderIds if order.routedOrderIds else [])
        update = order.markAsShipped(shipment=shipment)
        return {**shipment.to_dict(), **update}

def getBatch(enterpriseId:str, batchId:str):
    ref = db.document(f"enterprises/{enterpriseId}/batches/{batchId}").get()
    if ref.exists: return ref.to_dict()


def shipBatch(params: Params):
    user = params.currentUser
    batchId = params.args.get("batchId")
    batch = getBatch(user.enterpriseId, batchId)
    if not batch: raise API_Error("Batch not found.", 404)
    orderIds = batch.get("orderIds")
    shippedOrders = []
    app = Application.get("EASYPOST"+user.enterpriseId, type="shipping")
    for orderId in orderIds:
        try:
            shipped = shipNow(Params(
                user, 
                params.hostname,
                id=None,
                args=dict(
                    orderId=orderId,
                    app=app
                )
            ))
            if shipped: shippedOrders.append(orderId)
        except Exception as e: 
            print(e, orderId)
    return dict(shippedOrders=shippedOrders)

def grams2Ounc(grams):
    return round(float(grams)/28.35,2)

def getPackages(params:Params):
    carrierCode = params.args.get("carrierCode")
    platformId = params.args.get("platformId")
    app = Application.get(platformId + params.currentUser.enterpriseId, type = "shipping")
    if platformId == "SHIPSTATION":
        apiKey, apiSecret = app.apiKey, app.apiSecret
        url = "https://ssapi.shipstation.com/carriers/listpackages"
        response = requests.request("GET", url, auth=Authorization(apiKey, apiSecret), params=dict(carrierCode=carrierCode))
        if response.status_code == 200:
            packages = response.json()
            packages = [dict(
                id = package.get("code"),
                name = package.get("name"),
            ) for package in packages]
            return packages
    return []

def submitRemainingShipmentForOrder(params: Params):
    orderId = params.args.get("orderId")
    from V2.functions.Orders.main import Order
    order = Order.get(orderId)
    trackingCode = params.args.get("trackingCode")
    carrierName = params.args.get("carrierName")
    if order.platformId == "1":
        shop:Shop = Shop.get(order.shopId)
        from V2.functions.Etsy.Orders import \
            submitShipment as submitShipmentEtsy
        res = submitShipmentEtsy(shop, order.platformOrderId, trackingCode, carrierName)
        if not res:raise API_Error("Failed to submit shipment to Etsy")
        return dict(id=order.id)
    if order.platformId == "2":
        shop:Shop = Shop.get(order.shopId)
        from functions.Shopify.Fulfillment import \
            submitShopifyShipment as submitShipmentShopify
        res = submitShipmentShopify(shop, order.platformOrderId, carrierName, trackingCode)
        if not res:raise API_Error("Failed to submit shipment to Shopify")
        return dict(id=order.id)
    raise API_Error("Not supported")





def getRate(params:Params):
    user = params.currentUser
    uid, enterpriseId = user.uid, user.enterpriseId
    orderId = params.args.get("orderId")
    app = params.args.get("app", Application.get("EASYPOST"+user.enterpriseId, type="shipping"))
    from V2.functions.Orders.main import Order
    order = Order.get(orderId)
    if order:
        if order.shipped: return
        # metadata = order.metadata
        # shipping = None
        fromAddressId = 'GglHeQqlMKasLA3upoQc' if enterpriseId == "60D7GFDlMFFd6IsK1e58" else None
        # if metadata: shipping = order.metadata.get("shipping", {})
        # if not (order.shippingSpeedId or shipping): return shipNowSmartShipment(params)
        shippingSpeedId = params.args.get("shippingSpeedId")
        fromAddress = Address.get(fromAddressId) if fromAddressId else Address.getDefaultAddress(enterpriseId, uid=uid)
        if not fromAddress: raise API_Error("No from address selected, Please set default from address.", 400)
        orderItems = order.getOrderItems()
        blankVariantIds = set((VariantMapping.from_dict(orderItem.purchaseOrderData).blankProductId,VariantMapping.from_dict(orderItem.purchaseOrderData).blankVariantId) for orderItem in orderItems if not orderItem.ignored)
        blankVariants = [BlankVariant.get(blankProductId,blankVariantId) for blankProductId,blankVariantId in blankVariantIds]
        totalWeight = sum(blankVariant.weight for blankVariant in blankVariants)
        if enterpriseId == "gSo3G298DNypOMvofnaR": totalWeight = 4
        parcel = dict(
            height=params.args.get('height',2),
            weight = totalWeight if totalWeight>0 else 5,
            length = params.args.get('length',12),
            width = params.args.get('width',15),
        )
        shippingSpeed = getShippingSpeed(enterpriseId, shippingSpeedId)
        if not shippingSpeed: raise API_Error("Shipping speed not found or not set.")
        globalShippingSpeed = getGlobalShippingSpeed(shippingSpeed.get('carrierId', ''))
        if not globalShippingSpeed: raise API_Error("Shipping speed not found or not set.")
        carrierId, serviceId = globalShippingSpeed.get('platformCarrierId'), next((service.get("id") for service in globalShippingSpeed.get('services', []) if service.get("id") == shippingSpeed.get("serviceId")), None)
        platformId = globalShippingSpeed.get('platformId')
        packageId = shippingSpeed.get("packageId")
        # else:
        #     shippingSpeed = getShippingSpeed(enterpriseId, "<1lb" if totalWeight < 16 else ">1lb")
        #     if not shippingSpeed: raise API_Error("Shipping speed not found or not set.")
        #     globalShippingSpeed = getGlobalShippingSpeed(shippingSpeed.get('carrierId', ''))
        #     if not globalShippingSpeed: raise API_Error("Shipping speed not found or not set.")
        #     carrierId, serviceId = globalShippingSpeed.get('platformCarrierId'), next((service.get("id") for service in globalShippingSpeed.get('services', []) if service.get("id") == shippingSpeed.get("serviceId")), None)
        #     platformId = globalShippingSpeed.get('platformId')
        #     packageId = shippingSpeed.get("packageId")
        parcel = dict(
            height=params.args.get('height',2),
            weight = totalWeight if totalWeight>0 else 5,
            length = params.args.get('length',12),
            width = params.args.get('width',15),
        )
        from V2.functions.Shipments.EasyPost import getSelectedRate
        if platformId == "EASYPOST": rate = getSelectedRate(user,app,fromAddress, order, parcel, carrierId, serviceId)
        else:
            app = Application.get(platformId+enterpriseId, type="shipping")
            from functions.Shipments.Shipstation import \
                getRates as getShipstationRate
            rates = getShipstationRate(
                enterpriseId=enterpriseId,
                fromAddress=fromAddress.to_dict(),
                order=order.to_dict(),
                parcel=parcel,
                carrierId=carrierId,
                packageCode=packageId
            )
            rate = next((rate for rate in rates if rate.get("serviceId") == serviceId), None)
            if rate: return dict(cost=rate.get("cost"))
        if not rate: raise API_Error("Selected rate not available.", 404)
        return dict(cost = rate.get("rate"))