import json
import tempfile
import traceback
from datetime import datetime

import requests

from V2.functions.Applications.main import Application
from V2.functions.Etsy.Auth import EtsyClient
from V2.functions.Images.main import Image
from V2.functions.LastUpdate import LastUpdate
from V2.functions.Products.main import Product
from V2.functions.Products.Variants import Property, Variant
from V2.functions.Shops.main import Shop, getShopsByPlatformId
from V2.middlewares.auth import API_Error
from V2.Params import Params


def updateAllProducts(params:Params):
    shops = getShopsByPlatformId(platformId="1", credentials= True)
    shops = [s for s in shops if all([s.apiVersion == "v3",s.accessToken, s.tokenType, s.expiresAt])]
    return str([updateShopProducts(shop) for shop in shops])

def updateShopProducts(shop:Shop):
    try:    
        shopId = shop.id
        application = Application.get(shop.appId)
        update = LastUpdate.get(shopId, "products")
        currentPage = update.nextPage if update.nextPage else 0
        client = EtsyClient(application.apiKey,shop)
        limit = 100
        products, totalProducts = getEtsyProducts(client=client,platformShopId=shop.platformShopId, limit=limit, offset=limit*currentPage)
        print("updating etsy products for shop", shopId, len(products))
        totalPages = int(totalProducts/limit)+1
        savedProducts = []
        for product in products:
            platformProductId =str( product.get("listing_id"))
            images= [Image(url=i.get("url_fullxfull")) for i in product.get("images")]
            convertedProduct = Product(
                id=shop.platformId+shop.id+platformProductId,
                uid=shop.uid, 
                platformId=shop.platformId,
                shopId=shopId,
                enterpriseId=shop.enterpriseId,
                platformProductId=platformProductId,
                name = product.get("title"),
                description = product.get("description"),
                createdAt= datetime.fromtimestamp(product.get("original_creation_timestamp")),
                updatedAt= datetime.fromtimestamp(product.get("last_modified_timestamp")),
                price = float(product.get("price").get("amount")/product.get("price").get("divisor")),
                tags = list(product.get("tags", [])),
                images = images
            )
            productVariants = product.get("inventory").get("products")
            variants = [Variant(
                id=variant.get("product_id"),
                uid=shop.uid,
                enterpriseId=shop.enterpriseId,
                productId=convertedProduct.id,
                # ignore sku for app.myshirtpod.com
                sku = None if shop.enterpriseId == "lJ2eUFov5WyPlRm4geCr" else  variant.get('sku'),
                deleted=variant.get("is_deleted"),
                price=(variant.get("offerings", [])[0].get("price").get("amount")/variant.get("offerings", [])[0].get("price").get("divisor")) if variant.get("offerings", []) else 0,
                properties=[Property(
                    id=prop.get("property_id"),
                    name=prop.get("property_name"),
                    value=",".join(list(prop.get("values")))
                ) for prop in variant.get("property_values")]
            ) for variant in productVariants]
            savedProducts.append(convertedProduct.save(variants=variants))
        update.save(
            count=len(savedProducts),
            lastPage=currentPage,
            total=totalProducts,
            nextPage=currentPage+1 if totalPages > currentPage else 1
        )
    except Exception as e:
        print(traceback.format_exc())
        return str(e)
    return f"Products Updated => {len(savedProducts)}"


def updateSingleEtsyProduct(shop: Shop, platformProductId:str):
    app = Application.get(shop.appId)
    client = EtsyClient(app.apiKey, shop)
    product = getEtsyProduct(client, platformProductId)
    platformProductId =str(product.get("listing_id"))
    images= [Image(url=i.get("url_fullxfull")) for i in product.get("images", [])]
    productVariants = product.get("inventory").get("products")
    convertedProduct = Product(
        id=shop.platformId+shop.id+platformProductId,
        uid=shop.uid, 
        platformId=shop.platformId,
        shopId=shop.id,
        enterpriseId=shop.enterpriseId,
        platformProductId=platformProductId,
        name = product.get("title"),
        description = product.get("description"),
        createdAt= datetime.fromtimestamp(product.get("original_creation_timestamp")),
        updatedAt= datetime.fromtimestamp(product.get("last_modified_timestamp")),
        price = float(product.get("price").get("amount")/product.get("price").get("divisor")),
        tags = list(product.get("tags", [])),
        images = images
    )
    variants = [Variant(
        id=variant.get("product_id"),
        uid=shop.uid,
        enterpriseId=shop.enterpriseId,
        productId=convertedProduct.id,
        sku=variant.get("sku"),
        deleted=bool(variant.get("is_deleted")),
        price=(variant.get("offerings", [])[0].get("price").get("amount")/variant.get("offerings", [])[0].get("price").get("divisor")) if variant.get("offerings", []) else 0,
        properties=[Property(
            id=prop.get("property_id"),
            name=prop.get("property_name"),
            value=",".join(list(prop.get("values")))
        ) for prop in variant.get("property_values")]
    ) for variant in productVariants]
    return convertedProduct.save(variants)


def getEtsyProducts(client:EtsyClient, platformShopId:str, limit:int=100, offset:int=0) -> tuple[list, int]:
    url = f"https://openapi.etsy.com/v3/application/shops/{platformShopId}/listings"
    res = client.get(url, 
        params=dict(
            limit=limit,
            sort_on="updated",
            includes="Images,Inventory",
            offset=offset
        ),
    )
    if res.status_code == 200: return res.json().get("results"), res.json().get("count")
    raise API_Error(res.text, res.status_code)

def getEtsyProduct(client: EtsyClient, platformProductId:str):
    url = f"https://openapi.etsy.com/v3/application/listings/{platformProductId}"
    res = client.get(url, params=dict(
        includes="Images,Inventory"
    ))
    if res.status_code == 200: return res.json()
    raise API_Error(res.text, res.status_code)

def getEtsyProductVariants(client: EtsyClient, platformProductId:str) -> list[dict]:
    url = f"https://openapi.etsy.com/v3/application/listings/{platformProductId}/inventory"
    res = client.get(url, params=dict(
        show_deleted=True,
    ))
    print(res.text, res.status_code)
    if res.status_code == 200: return res.json().get("products", [])
    raise API_Error(res.text, res.status_code)

def submitEtsyProduct(
        shop,
        title,
        description,
        price,
        quantity,
        taxonomy_id,
        tags,
        state,
        variants,
        shipping_profile_id,
        images
    ):
    shop = Shop.from_dict(shop)
    app = Application.get(shop.appId)
    client = EtsyClient(app.apiKey, shop)
    url = f"https://openapi.etsy.com/v3/application/shops/{shop.platformShopId}/listings"
    listing = dict(
        title=title,
        description=description,
        price=price if price else min([float(v.get('price')) for v in variants]),
        quantity=quantity, 
        taxonomy_id=int(taxonomy_id),
        tags=",".join(tags),
        who_made="i_did",
        is_supply=False,
        when_made="made_to_order",
        recipient="not_specified",
        state=state,
        shipping_profile_id= shipping_profile_id
    )
    request = client.post(url, data=listing)
    if request.status_code == 200:
        listing = request.json().get("results")
        listing_id = str(listing.get('listing_id'))
        variants = generate_variants("1", variants, listing_id)
        inventoryUrl = f"https://openapi.etsy.com/v3/application/listings/{listing_id}/inventory"
        inventory_request = client.put(f"{inventoryUrl}/{listing_id}/inventory", data = variants)
        if inventory_request.status_code != 200:
            deletedRequest = client.delete(f"https://openapi.etsy.com/v3/application/listings/{listing_id}")
            raise API_Error(inventory_request.text, 400, meta= dict(message = inventory_request.text, deleteMessage = deletedRequest.text))
        rank = 1
        path, image_paths = tempImages(images)
        for image in image_paths:
            with open(image.get("image"), 'rb') as f:
                files = {"image": f}
                image_request = client.post(f"https://openapi.etsy.com/v3/application/shops/{shop.platformShopId}/listings/{listing_id}/images", files = files, params={"rank":rank})
                if image_request.status_code != 201:
                    deletedRequest = client.delete(f"https://openapi.etsy.com/v3/application/listings/{listing_id}")
                    raise API_Error("Image upload failed. ", 400, meta=dict(message = image_request.text, deleteMessage = deletedRequest.text))
                rank += 1
        if inventory_request.status_code == 200:
            etsy_variants = {str(v.get('sku')): str(v.get('product_id')) for v in inventory_request.json().get("results", {}).get("products", [])}
            return listing_id, etsy_variants
        raise API_Error(inventory_request.text, inventory_request.status_code)
    raise API_Error(request.text, request.status_code)


def generate_variants(platformId:str,variants:list, listing_id=None):
    if platformId  == "1":
        formatted_variants =  []
        size_property_id = 513
        color_property_id = 514
        for variant in variants:
            size =  variant.get("size")
            color = variant.get("color")
            variant = dict(
                sku = variant.get("sku"),
                property_values = [
                    dict(
                        property_id = size_property_id,
                        property_name = "Size",
                        values = [size]
                    ),
                    dict(
                        property_id = color_property_id,
                        property_name = "Primary Color",
                        values = [color]
                    )
                    ],
                offerings = [dict(price=float(variant.get("price")), quantity=variant.get("quantity", 100), is_enabled=True)]
            )
            formatted_variants.append(variant)
        return dict(products = formatted_variants, price_on_property=[size_property_id], sku_on_property=[size_property_id,color_property_id])
 
    
def tempImages(images:list):
    path = tempfile.mkdtemp()
    image_paths = []
    for image in images:
        src = image.get('url')
        name = image.get("name")
        content = requests.get(src).content
        image_name = f"{path}/{name}"
        with open(image_name, "wb") as image_file:
            image_file.write(content)
            image_paths.append(dict(name=name, image=image_name))
    return (path, image_paths)

    
def tempImage(name:str, src:str):
    path = tempfile.mkdtemp()
    content = requests.get(src).content
    path = f"{path}/{name}"
    with open(path, "wb") as image_file:
        image_file.write(content)
    return path


def getTaxonomy(params: Params):
    url = f"https://openapi.etsy.com/v3/application/seller-taxonomy/nodes"
    shop = Shop.get(params.args.get("shopId"))
    app = Application.get(shop.appId)
    client = EtsyClient(app.apiKey, shop)
    res = client.get(url)
    if res.status_code == 200:
        return res.json()
    raise API_Error(res.text, res.status_code)

# This function submits a product to Etsy. It takes in the user ID, enterprise ID, shop, 
# title, description, price, quantity, taxonomy ID, tags, state, variants, shipping template ID, and images as parameters.
#  It uses the Etsy API to submit the product and upload the images and returns a tuple with the listing ID and Etsy variants.
#  If there is an issue with the request, an API Error is raised.

import os


def submitToEtsy(uid:str,app:Application, shop:Shop, title:str, description:str, price:float, quantity:int, taxonomy_id:int, tags:list,
    state: str,variants:list,  shipping_template_id:int, colorImages:dict ={}, **kwargs) -> tuple:
    client = EtsyClient(app.apiKey, shop)
    listings_url  = f"https://openapi.etsy.com/v3/application/shops/{shop.platformShopId}/listings"
    listing = dict(
        title=title,
        description=description,
        price=price if price else min([float(v.get('price')) for v in variants]),
        quantity=quantity, 
        taxonomy_id=int(taxonomy_id),
        tags=",".join(tags),
        who_made="i_did",
        is_supply=False,
        when_made="made_to_order",
        recipient="not_specified",
        state=state,
        shipping_profile_id= shipping_template_id,
        **kwargs
    )
    request = client.post(listings_url, json=listing)
    if request.status_code == 201:
        listing = request.json()
        listing_id = str(listing.get('listing_id'))
        inventoryUrl = f"https://openapi.etsy.com/v3/application/listings/{listing_id}/inventory"
        variants = generate_variants("1", variants, listing_id)
        inventory_request = client.put(inventoryUrl, json=variants)
        if inventory_request.status_code != 200:
            deletedRequest = client.delete(f"https://openapi.etsy.com/v3/application/listings/{listing_id}")
            raise API_Error(inventory_request.text, 400, meta= dict(message = inventory_request.text, deleteMessage = deletedRequest.text))
        imagesUrl = f"https://openapi.etsy.com/v3/application/shops/{shop.platformShopId}/listings/{listing_id}/images"
        images = []
        for color, colorImage in colorImages.items():
            images.extend([dict(
                url = i.get("url"),
                color = color,
                name = i.get("name"),
                placement= i.get("placement")
            ) for i in colorImage])
        import posixpath
        rank = 1
        doneColorsPlacements = []
        # print(images)
        for image in images:
            name = image.get("name")
            placement = image.get("placement")
            color = image.get("color")
            if  color+"1" in doneColorsPlacements:
                # print(color+"1", doneColorsPlacements)
                continue
            if not name:  name = image.get("color", "") + posixpath.basename(image.get("url"))
            path = tempImage(name, image.get("url"))
            data = {
                "name": name,
                "rank": rank
            }
            files = {
                "image": open(path, "rb").read(),
                "name": name
            }
            image_request = client.upload(
                    imagesUrl,
                    files=files,  # Use `files` instead of `data`
                    data=data,  # Pass rank separately in `data`
                )
            if image_request.status_code != 201: 
                    print("Image upload failed. ",image_request.request.headers, image_request.url, image_request.status_code, image_request.text)
                    return
            image["image_id"] = image_request.json().get("listing_image_id")
            rank += 1
            os.remove(path)
            doneColorsPlacements.append(color+placement)
            image['color_value_id'] = get_color_value_id(image.get("color"), inventory_request.json())
        colorImageUpdated = update_variation_image(client, listing_id, 
                                        [
                                            {
                                                "image_id": colorImage.get("image_id"),
                                                "property_id": 514,
                                                "value_id": colorImage.get("color_value_id")
                                            } for colorImage in images if colorImage.get("image_id") and colorImage.get("color_value_id")
                                        ])
        if inventory_request.status_code == 200:
            etsy_variants = {str(v.get('sku')): str(v.get('product_id')) for v in inventory_request.json().get("products", {})}
            return listing_id, etsy_variants
        raise API_Error(inventory_request.text, inventory_request.status_code)
    raise API_Error(request.text, request.status_code)

def get_color_value_id(color, variants):
    for variant in variants.get("products", []):
        for prop in variant.get("property_values", []):
            if prop.get("property_id") == 514 and color in prop.get("values") and prop.get("value_ids"):
                return prop.get("value_ids")[0]
    return None

def update_variation_image(client: EtsyClient, listing_id, variation_images):
    url = f"https://openapi.etsy.com/v3/application/shops/{client.shop.platformShopId}/listings/{listing_id}/variation-images"
    data = {
        "variation_images":variation_images
    }
    response = client.post(url, json=data)
    if response.status_code not in [201, 200]: return False
    return True

def getShippingProfile(client:EtsyClient, shippingProfileId:str):
    url = f"https://openapi.etsy.com/v3/application/shops/{client.shop.platformShopId}/shipping-profiles/{shippingProfileId}"
    res = client.get(url)
    if res.status_code == 200:
        return res.json()
    raise API_Error(res.text, res.status_code)