import json
import os
import re
import threading
import traceback
from datetime import datetime

import requests
from zeep import helpers

from configs.firebase import SERVER_TIMESTAMP, ArrayRemove, db
from functions.LastUpdated import bankProductsLastUpdated
from functions.Response import API_Error
from functions.Storage import get_file_path, saveFile
from functions.Suppliers.Auth import (CREDS, getClient, getSupplierCredentials,
                                      getSuppliers)
from functions.Suppliers.Images import getBlankImages
from functions.Suppliers.Locations import saveLocation


def updateNewProducts(data):
    sups = getSuppliers(True)
    for supplier in sups:
        supplierId = supplier.get('supplierId')
        productIds = list(set([p.get('id') for p in products]))
        products = [dict(productId= p,id =supplierId ) for p in productIds]
        for product in products:
            updateBlankProducts(product)

def updateNextBlank(data):
    suppliers = CREDS.keys()
    updates = []
    for supplier in suppliers:
        ref = db.collection("suppliers").document(supplier).collection("misc").document("blankProductIds").get()
        blankProductIds = ref.to_dict()
        if blankProductIds:
            blankProductIds = blankProductIds.get('blankProductIds')
            if blankProductIds:
                blankProductId = blankProductIds[0]
                res = updateBlankProducts(dict(productId=blankProductId, id=supplier))
                if res:
                    ref.reference.update({'blankProductIds': ArrayRemove([blankProductId]), "updatedAt": SERVER_TIMESTAMP, "lastProductId": blankProductId})
                    updates.append(res)
                    print("Auto updated blank product: ",supplier,blankProductId)
    return updates


def updateBlankProducts(data):
    supplierId, productId = data.get('id'), data.get('productId')
    try: client, version = getClient(supplierId, 'products')
    except Exception as e:
        print(e)
        return f"SUPPLIER SERVER DOWN {supplierId} {e}"
    creds = getSupplierCredentials(supplierId)
    try:
            blankProduct = getProduct(client, version, productId,creds.get('username'), creds.get('password'))
            if blankProduct:
                ProductKeywordArray = blankProduct.get('ProductKeywordArray', {})
                ProductCategoryArray = blankProduct.get('ProductCategoryArray', {})
                if ProductCategoryArray:
                    categories= [c.get('category') for c in ProductCategoryArray.get('ProductCategory', [])]
                else:
                    categories = []
                # if not any(["T-Shirts" in categories, "Hats" in categories, "Sweatshirts" in categories]):
                #     print("Not T-Shirts/Hats/Sweatshirts",supplierId, productId)
                #     return f"Not T-Shirts/Hats/Sweatshirts {supplierId} {productId}"
                # else: print("Updating",supplierId, productId)
                # locations = blankProduct.get('FobPointArray', {})
                # locations = locations.get('FobPoint', []) if locations else []
                # for location in locations:
                #     saveLocation(
                #         supplierId=supplierId, 
                #         id=location.get('fobId'), 
                #         name = location.get('fobCity'),
                #         state = location.get('fobState'),
                #         city = location.get('fobCity'),
                #         zip = location.get('fobPostalCode'),
                #         country = location.get('fobCountry'),
                #         )
                tags = []
                if ProductKeywordArray:
                    for keyword in list(ProductKeywordArray.get('ProductKeyword', [])):
                            tags.extend(keyword.get('keyword', "").split(','))
                convertedBlankProduct = convertBlankProduct(
                    id = blankProduct.get('productId'),
                    name = blankProduct.get('productName'),
                    createdAt = datetime.now(),
                    updatedAt= datetime.now(),
                    description= ", ".join(blankProduct.get('description', [])),
                    brand= blankProduct.get('productBrand'),
                    primaryImage= blankProduct.get('primaryImageURL'),
                    supplierId= supplierId,
                    supplierName= creds.get('name'),
                    public=True,
                    tags = tags ,
                    categories=categories
                )
                images = getBlankImages(convertedBlankProduct)
                convertedImages =[]
                savedImages = set(image.get("url") for image in images if (image.get('ClassTypeArray', {}).get("ClassType", [])[0].get('classTypeName', None) if image.get('ClassTypeArray', {}).get("ClassType", [])[0 if supplierId !="AB" else 1] else None) in ["Primary", "Rear", "Swatch", "Front"])
                # print(savedImages)
                if supplierId in ["SANMAR", "AB"]: savedImages = {image: saveBlankImage(image) for image in savedImages}
                colors = []
                for image in images:
                    classTypeName = image.get('ClassTypeArray', {}).get("ClassType", [])[0 if supplierId !="AB" else 1].get('classTypeName', None) if image.get('ClassTypeArray', {}).get("ClassType", [])[0 if supplierId !="AB" else 1] else None
                    if classTypeName in ["Primary", "Rear", "Swatch", "Front"]:
                        convertedImages.append(dict(
                            id=None,
                            url=image.get('url') if supplierId not in ["SANMAR" , "AB"] else (savedImages.get(image.get("url")) if savedImages.get(image.get("url")) else image.get("url")),
                            blankVariantId=image.get('partId'),
                            type=classTypeName,
                            color = image.get('color')
                        ))
                    colors.append(image.get('color'))
                colors = set(colors)
                convertedBlankProduct['primaryImage'] = convertedImages[0].get('url') if convertedImages else None
                # print(convertedImages)
                convertedImages = {color: [image for image in convertedImages if image.get('color') == color] for color in colors}
                # print(convertedImages)
                # convertedBlankProduct['images'] = convertedImages
                # print(convertedImages)
                # convertedBlankProduct['primaryImage'] = next((image.get('url') for image in images if (image.get('ClassTypeArray', {}).get("ClassType", [])[0 if supplierId !="AB" else 1].get('classTypeName', None) if image.get('ClassTypeArray', {}).get("ClassType", [])[0 if supplierId !="AB" else 1] else None) == "Primary"), None)
                codes = {}
                if supplierId == "SANMAR":
                    codes = db.collection("settings").document("PMS_CODES").get().to_dict()
                    colorCodes = {b.get('ColorArray', {}).get('Color', [])[0].get('approximatePms'): getColorCode(b, supplierId, codes) for b in blankProduct.get('ProductPartArray', {}).get('ProductPart', [])}
                    notFound = {}
                    for pms,colorCode in colorCodes.items(): 
                        if pms and not colorCode: notFound[pms] = None
                    if notFound:
                        db.collection('settings').document("PMS_CODES").set(notFound, merge=True)
                # print(json.dumps({
                #     blankVariant.get("partId"): dict(blankVariant.get('Dimension')) for blankVariant in blankProduct.get('ProductPartArray', {}).get('ProductPart', [])
                # }, indent=4, default=str))
                size = ""
                blankVariants = []
                ProductPart =  blankProduct.get('ProductPartArray', {}).get('ProductPart', [])
                for blankVariant in ProductPart:
                    ApparelSize = blankVariant.get('ApparelSize', {}) if blankVariant.get('ApparelSize', {}) else {}
                    blankVariants.append(
                        convertBlankVariant(
                            id = blankVariant.get('partId'),
                            blankProductId= f'{supplierId}{blankProduct.get("productId")}',
                            description= ", ".join(blankVariant.get('description')),
                            gtin = blankVariant.get('gtin'),
                            weight = blankVariant.get('Dimension').get('weight'),
                            weightUnit = blankVariant.get('Dimension').get('weightUom'),
                            size = ApparelSize.get('labelSize', "OS").upper() if ApparelSize.get('labelSize', "OS").upper() != 'CUSTOM' else ApparelSize.get('customSize', "OS"),
                            style= ApparelSize.get('apparelStyle', ""),
                            colorCode=getColorCode(blankVariant, supplierId, codes),
                            color= blankVariant.get('ColorArray', {}).get('Color', [])[0].get('colorName') if blankVariant.get('ColorArray', {}).get('Color', []) else None, 
                            images = convertedImages.get(blankVariant.get('ColorArray', {}).get('Color', [])[0].get('colorName') if blankVariant.get('ColorArray', {}).get('Color') else None),
                    ))
                saveBlankProduct(convertedBlankProduct, blankVariants)
                return f"BLANK PRODUCT UPDATED: {supplierId}{productId}"
            else: print("No blank product found/ Error",supplierId, productId)
    except Exception as e:
        print(traceback.print_exc())
        return print("No blank product found/ Error",supplierId ,productId, e)

def saveBlankImage(url:str):
    try:
        name = url.replace("/", "-")
        path = get_file_path(name)
        with open(path, 'wb') as f:
            f.write(requests.get(url).content)
        file = saveFile(uid=None, enterpriseId=None,fileName=name, source_file_name=path,type='blankVariants')
        os.remove(path)
        return file.get("url")
    except Exception as e:
        print(e)
        return url

def getColorCode(blankVariant, supplierId, codes = {}):
    Color = blankVariant.get('ColorArray', {}).get('Color', [])
    if Color:
        Color = blankVariant.get('ColorArray', {}).get('Color', [])[0].get('hex')
        if supplierId == "AB" and Color:
            Color = str("#" + Color).lower()
    if not Color:
        approximatePms = blankVariant.get('ColorArray', {}).get('Color', [])[0].get('approximatePms')
        Color = codes.get(str(approximatePms).upper())
        if not Color: Color = codes.get(str(approximatePms).upper().replace(" ", ""))
        if not Color: Color = codes.get(approximatePms)
        if not Color: Color = codes.get(str(approximatePms).capitalize())
    return Color
def getProductsSellable(supplierId):
    creds = getSupplierCredentials(supplierId)
    client, version = getClient(supplierId, 'products')
    res = client.service.getProductSellable(
        wsVersion = version,
        id = creds.get('username'),
        password = creds.get('password'),
        isSellable=True
        )
    res = helpers.serialize_object(res, target_cls=dict).get('ProductSellableArray').get('ProductSellable')
    return res

def getProductsUpdated(client, version, username, password=None) -> list:
    lastUpdate = bankProductsLastUpdated()
    res = client.service.getProductDateModified(
        wsVersion = version,
        id = username,
        password = password,
        changeTimeStamp=datetime.fromtimestamp(lastUpdate.get('timestamp')).isoformat()
        )
    print(res)
    res = helpers.serialize_object(res, target_cls=dict)
    ProductDateModifiedArray = res.get('ProductDateModifiedArray')
    if ProductDateModifiedArray:
        return ProductDateModifiedArray.get('ProductDateModified', [])
    return []
    # raise API_Error(res.get('ServiceMessageArray', {}).get('ServiceMessage', {})[0].get('description'), 500, dict(res))

def getProduct(client, version,productId, username , password=None):
    try:
        res = client.service.getProduct(
            wsVersion = version,
            id = username,
            password = password,
            localizationCountry = "US",
            localizationLanguage  = "en",
            productId = productId
            )
        res = helpers.serialize_object(res, target_cls=dict)
        if not res.get("Product"):
            raise API_Error(res.get('ServiceMessageArray', {}).get('ServiceMessage', {})[0].get('description'), 500, dict(res))
        return res.get('Product', {})
    except Exception as e:
        print(e)
        return None


def blankProduct(blankProduct:dict, blankVariants=[]):
    id = blankProduct.get('id')
    _, ref = db.collection('blankProducts').document(id).set(blankProduct, merge=True)
    for blankVariant in blankVariants:
        ref.collection('blankVariants').document(blankVariant.get('id')).set(blankVariant, merge=True)
    return None

def convertBlankProduct(
    id,
    name, 
    createdAt, 
    updatedAt, 
    description:str, 
    primaryImage:str,
    brand:str, 
    supplierId:str, 
    supplierName:str,
    style=None,
    uid=None, 
    enterpriseId=None, 
    public=True, 
    tags=[], 
    categories=[]):
    blankProduct = dict(
        id = f"{supplierId}{id}",
        blankProductId = id,
        name=name, 
        createdAt=createdAt,
        updatedAt=updatedAt,
        description=removehtml(description),
        categories=categories,
        primaryImage= primaryImage,
        brand=brand,
        supplierId=supplierId,
        supplierName = supplierName,
        style=style,
        uid=uid,
        enterpriseId=enterpriseId,
        public=public,
        tags = tags,
    )
    return blankProduct



def convertBlankVariant(
    id,
    blankProductId,
    description=None,
    gtin=None,
    weight=None,
    weightUnit = "OZ",
    size=None,
    color=None,
    style=None, 
    images=[],
    colorCode = None,
        ):
    return dict(
        id=id,
        blankProductId=blankProductId,
        description=description,
        gtin=gtin,
        weight=float(weight)*16 if weightUnit.upper() == "LB" else float(weight),
        weightUnit ="OZ" if weightUnit == "LB" else weightUnit,
        size=size,
        color=color,
        style=style,
        images = images,
        colorCode=colorCode
        )
        
def removehtml(raw_html):
  cleanr = re.compile('<.*?>')
  cleantext = re.sub(cleanr, ' ', raw_html)
  return cleantext


def saveBlankProduct(blankProduct:dict, blankVariants = []):
    ref = db.collection("blankProducts").document(blankProduct.get('id'))
    ref.set(blankProduct, merge=True)
    # blankVariantIds = [b.get("id") for b in blankVariants]
    # currentBlankVariantIds = [b for b in ref.collection("blankVariants").get()]
    # deletedBlankVariants = [b for b in currentBlankVariantIds if b.id not in blankVariantIds]
    for b in blankVariants:
        b['deleted']=False
        # print("deleted", b.id)
    worker(saveBlankVariant, blankVariants)
    return ref.id

def saveBlankVariant(blankVariant:dict):
    ref = db.collection("blankProducts").document(blankVariant.get('blankProductId')).collection('blankVariants').document(blankVariant.get('id'))
    ref.set(blankVariant, merge=True)
    return ref.id

def getBlankProduct(id:str):
    ref = db.collection("blankProducts").document(id).get()
    if ref.exists:
        return ref.to_dict()
    return None

def getBlankVariant(blankProductId, blankVariantId):
    ref = db.collection("blankProducts").document(blankProductId).collection("blankVariants").document(blankVariantId).get()
    if ref.exists:
        return ref.to_dict()
    return None

def getBlankVariants(blankProductId:str):
    ref = db.collection('blankProducts').document(blankProductId).collection("blankVariants").get()
    return [r.to_dict() for r in ref]


def worker(function, array = []):
    try:
        thread_list = []
        for item in array:
            thread = threading.Thread(target=function, args=(item,))
            thread_list.append(thread)
            thread_list[array.index(item)].start()
        for thread in thread_list:
            thread.join()
        return len(array)
    except Exception as e:
        print(e)
        return 

    
import gc  # garbage collector


def getBlanksUsedByEnterprises():
    blankProductIds = set()
    enterprise_refs = db.collection("enterprises").stream()  # streaming avoids full loading

    for enterprise_doc in enterprise_refs:
        enterprise_id = enterprise_doc.id

        # Only fetch the needed subcollection
        blanks_ref = db.collection(f"enterprises/{enterprise_id}/blankProducts").get()
        for doc in blanks_ref:
            if doc.id in ["SS", "SANMAR", "AB"]:
                blankProductIds.add(doc.id)

        # Explicit cleanup (optional)
        del blanks_ref
        gc.collect()

    return list(blankProductIds)

from functions.Suppliers.Inventory import updateBlankInventory
from functions.Suppliers.Pricing import updateBlankPricing, updateSSPricing


def updateBlankInventoryPricings(params):
    blankProductId = params.get("blankProductId")
    if blankProductId:
        blankProduct = db.collection("blankProducts").document(blankProductId).get()
        if not blankProduct.exists: return None
        blankProductId = blankProduct.id
        if blankProduct.get("supplierId") == "SS": pricingUpdated = updateSSPricing(blankProductId)
        else:
            pricingUpdated = updateBlankPricing(dict(
                id  =   blankProductId,
            ))
        inventoryUpdated = updateBlankInventory(dict(
            id  =   blankProductId,
        ))
        db.document(f"blankProducts/{blankProductId}").update({
            "updates.pricing.status": "success" if pricingUpdated else "failed",
            "updates.pricing.timestamp":str(int( datetime.now().timestamp())),
            "updates.inventory.status": "success" if inventoryUpdated else "failed",
            "updates.inventory.timestamp":str(int( datetime.now().timestamp()))
        })
        return
    for supplierId in ["SS", "SANMAR"]:
        query = db.collection("blankProducts").where("supplierId", "==", supplierId).order_by("updates.pricing.timestamp").limit(1).get()
        if len(query)>1:
            for doc in query:
                blankProductId = doc.id
                if supplierId == "SS": pricingUpdated = updateSSPricing(blankProductId)
                else:
                    pricingUpdated = updateBlankPricing(dict(
                        id  =   blankProductId,
                    ))
                inventoryUpdated = updateBlankInventory(dict(
                    id  =   blankProductId,
                ))
                db.document(f"blankProducts/{blankProductId}").update({
                    "updates.pricing.status": "success" if pricingUpdated else "failed",
                    "updates.pricing.timestamp":str(int( datetime.now().timestamp())),
                    "updates.inventory.status": "success" if inventoryUpdated else "failed",
                    "updates.inventory.timestamp":str(int( datetime.now().timestamp()))
                })
            return
        if not query: return None
        blankProductId = query[0].id
        print("Updating blank product inventory and pricing", blankProductId)
        if supplierId == "SS": pricingUpdated = updateSSPricing(blankProductId)
        else:
            pricingUpdated = updateBlankPricing(dict(
                id  =   blankProductId,
            ))
        inventoryUpdated = updateBlankInventory(dict(
            id  =   blankProductId,
        ))
        db.document(f"blankProducts/{blankProductId}").update({
            "updates.pricing.status": "success" if pricingUpdated else "failed",
            "updates.pricing.timestamp":str(int( datetime.now().timestamp())),
            "updates.inventory.status": "success" if inventoryUpdated else "failed",
            "updates.inventory.timestamp":str(int( datetime.now().timestamp()))
        })

