import sys
import aiohttp
from aiohttp import ClientTimeout
import asyncio
import json
import time
from datetime import timedelta
import random
import os
import inspect
import smtplib
import csv
from datetime import datetime
from email import encoders
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from boutiquenature import login_to_dietpro, is_session_active, close_session, get_ldjson_from_url
from dpnature import wait_random, login_to_dpnature, is_logged_in, get_stock_variant_dp_nature, get_stock_product_without_variant, initialize_driver
from selenium.common.exceptions import NoSuchElementException, WebDriverException
from logger import send_log

############################################### Var definition Start

shop_link = "naturellementbiocom.myshopify.com"
access_token = "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"
api_version = "2025-04"
all_products = []
email = "ecomcordo@gmail.com"
password = "Protozorekami59@"
session = None
processed_products = {
    "treated": [],
    "not_needed": [],
    "not_concerned": [],
    "failed": []
}

############################################### Var definition End

############################################### GET Start

# recuperation des produits de la boutique shopify
async def get_all_products():
    global shop_link, access_token, api_version
    all_products = []
    limit = 250
    last_product_id = 0
    sleeping = 0

    async with aiohttp.ClientSession() as session:
        while True:
            if sleeping == 2:
                sleeping = 0
                await asyncio.sleep(1)
            sleeping += 1

            url = f"https://{shop_link}/admin/api/{api_version}/products.json?limit={limit}&since_id={last_product_id}&status=active"
            headers = {"X-Shopify-Access-Token": access_token}

            async with session.get(url, headers=headers) as response:
                if response.status not in [200, 201]:
                    send_log(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "Naturellement Bio", __file__, 900, inspect.currentframe().f_code.co_name, "Erreur de récupération des produits", inspect.currentframe().f_lineno, "error")
                    break

                data = await response.json()
                if "products" not in data or not data["products"]:
                    break
                products = data.get("products", [])
                all_products.extend(products)
                if not data["products"]:
                    break
                last_product_id = products[-1]["id"]

        with open("products.json", "w") as file:
            json.dump({"products": all_products}, file)

        return json.dumps({"products": all_products})

# recuperation des metafields d'un produit
async def get_metafield_product(product_id, session):
    global shop_link, access_token, api_version

    query = """
    query {
        product(id: "%s") {
            metafields(first: 50) {
                edges {
                    node {
                        id
                        key
                        namespace
                        value
                    }
                }
            }
        }
    }
    """ % product_id

    url = f"https://{shop_link}/admin/api/{api_version}/graphql.json"
    headers = {
        "X-Shopify-Access-Token": access_token,
        "Content-Type": "application/json"
    }

    try:
        async with session.post(url, headers=headers, json={"query": query}) as response:
            if response.status not in [200, 201]:
                send_log(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "Naturellement Bio", __file__, 900, inspect.currentframe().f_code.co_name, f"Erreur HTTP {response.status} lors de la récupération des metafields", inspect.currentframe().f_lineno, "error"
                )
                return None

            data = await response.json()

            throttle_status = data.get("extensions", {}).get("cost", {}).get("throttleStatus", {})
            currently_available = throttle_status.get("currentlyAvailable", 0)
            requested_query_cost = data.get("extensions", {}).get("cost", {}).get("requestedQueryCost", 0)

            if currently_available < requested_query_cost:
                await asyncio.sleep(1)
                return await get_metafield_product(product_id, session)

            return data.get("data", {}).get("product", {}).get("metafields", {}).get("edges", [])
    except Exception as e:
        send_log(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "Naturellement Bio",  __file__, 900, inspect.currentframe().f_code.co_name, f"Exception aiohttp : {str(e)}", inspect.currentframe().f_lineno, "error")
        return None


############################################### GET End

############################################### Read Start

async def read_product(driver, all_product, session, max_duration=3300):    
    global processed_products
    processed_products = {
        "treated": [],
        "not_needed": [],
        "not_concerned": [],
        "failed": []
    }
    
    def map_inventory_policy(value):
        return "en_stock_naturellement_bio" if value == "continue" else "rupture_naturellement_bio"
    all_product_array = json.loads(all_product)
    formated_date = datetime.now().strftime("%d-%m-%Y")
    start_time = time.time()
    i = 0
    try:
        with open("last_iteration.txt", "r") as f:
            i = int(f.read().strip())
            last_modified_date = (datetime.fromtimestamp(os.path.getmtime("last_iteration.txt")) - timedelta(days=1)).strftime("%d-%m-%Y")
            if i == 0 and last_modified_date == formated_date:
                print("Le fichier a été modifié aujourd'hui et i est égal à 0.")
                send_log(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "Naturellement Bio", __file__, 900, inspect.currentframe().f_code.co_name, "Les produits ont déjà été traités aujourd'hui", inspect.currentframe().f_lineno, "error")
                return    
    except FileNotFoundError:
        send_log(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "Naturellement Bio", __file__, 900, inspect.currentframe().f_code.co_name, "Erreur de lecture du fichier last_iteration.txt", inspect.currentframe().f_lineno, "error")
        return
    if "products" not in all_product_array or not isinstance(all_product_array["products"], list):
        send_log(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "Naturellement Bio", __file__, 900, inspect.currentframe().f_code.co_name, "Erreur de format des produits", inspect.currentframe().f_lineno, "error")
        return
    print("Reprise a l'itération :", i)
    for product in all_product_array["products"][i:]:
        print(i)
        if time.time() - start_time > max_duration:
            with open("last_iteration.txt", "w") as f:
                f.write(str(i))
            print(f"Temps limite atteint. Dernière itération sauvegardée : {i}")
            save_processed_products_to_file()
            return
        i += 1
        
        # if product["id"] != 15040906920313:
        #     print("skipped product for debug")
        #     continue
        
        if len(product["variants"]) > 1:
            processed_products["not_concerned"].append({
                "id": product["id"], 
                "title" : product["title"],"status": "not_concerned",
                "stock_initial": map_inventory_policy(product["variants"][0]["inventory_policy"]),
                "stock_fournisseur": "non_concerne",
                "stock_final": "non_concerne"
            })
            continue
        time.sleep(1)
        try:
            metafields = await get_metafield_product(product["admin_graphql_api_id"], session)
        except Exception as e:
            send_log(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "Naturellement Bio", __file__, 900, inspect.currentframe().f_code.co_name, f"Erreur de récupération des metafields : {e}", inspect.currentframe().f_lineno, "error")
            processed_products["failed"].append({
                "id": product["id"], 
                "title" : product["title"],"status": "failed",
                "stock_initial": map_inventory_policy(stock_shopify),
                "stock_fournisseur": "non_concerne",
                "stock_final": "non_concerne"
            })
            continue
        if not metafields:
            processed_products["failed"].append({
                "id": product["id"], 
                "title" : product["title"],"status": "failed",
                "stock_initial": map_inventory_policy(stock_shopify),
                "stock_fournisseur": "non_concerne",
                "stock_final": "non_concerne"
            })
            continue
        preorder = False
        url_dp_nature = None
        ref_dp_nature = None
        date_update_stock_huggii = None
        stockDpNature = None
        stock_shopify = product["variants"][0]["inventory_policy"]
        for metafield in metafields:
            key = metafield["node"]["key"]
            value = metafield["node"]["value"]
            if key == "date_preorder" and value != "":
                preorder = True

            if key == "date_update_stock_huggii" and value != "":
                date_update_stock_huggii = value
            if key == "url_grossiste" and value != "":
                urls = json.loads(value)
                for url in urls:
                    if "https://www.dpnature.fr/" in url:
                        url_dp_nature = url
            if key == "ref_dp_nature" and value != "":
                ref_dp_nature = value
        if preorder:
            processed_products["not_concerned"].append({
                "id": product["id"], 
                "title" : product["title"],"status": "not_concerned",
                "stock_initial": map_inventory_policy(stock_shopify),
                "stock_fournisseur": "non_concerne",
                "stock_final": "non_concerne"
            })
            continue
        if url_dp_nature is None:
            processed_products["not_concerned"].append({
                "id": product["id"], 
                "title" : product["title"],"status": "not_concerned",
                "stock_initial": map_inventory_policy(stock_shopify),
                "stock_fournisseur": "non_concerne",
                "stock_final": "non_concerne"
            })
            continue
        if date_update_stock_huggii:
            if datetime.strptime(date_update_stock_huggii, "%d-%m-%Y").date() == datetime.now().date():
                processed_products["not_needed"].append({
                    "id": product["id"], 
                    "title" : product["title"],"status": "not_needed",
                    "stock_initial": map_inventory_policy(stock_shopify),
                    "stock_fournisseur": "non_concerne",
                    "stock_final": "Deja traité aujourd'hui"
                })
                continue
        if url_dp_nature:
            if ref_dp_nature:
                stockDpNature = get_stock_variant_dp_nature(driver, url_dp_nature, ref_dp_nature)
                if stockDpNature is None:
                    processed_products["failed"].append({
                        "id": product["id"], 
                        "title" : product["title"],"status": "failed",
                        "stock_initial": map_inventory_policy(stock_shopify),
                        "stock_fournisseur": "erreur",
                        "stock_final": "erreur lors de la recupération de la variante sur dpnature"
                    })
                    continue
            else:
                stockDpNature = get_stock_product_without_variant(driver, url_dp_nature)
                if stockDpNature is None:
                    processed_products["failed"].append({
                        "id": product["id"], 
                        "title" : product["title"],"status": "failed",
                        "stock_initial": map_inventory_policy(stock_shopify),
                        "stock_fournisseur": "erreur",
                        "stock_final": "erreur lors de la récupération du produit sur dpnature"
                    })
                    continue
        has_stock = stockDpNature is True
        no_stock = stockDpNature is False
        print(stockDpNature)
        print(stock_shopify)
        must_be_update = None
        if has_stock and stock_shopify == "continue":
            must_be_update = False
        elif no_stock and stock_shopify == "deny":
            must_be_update = False
        elif has_stock and stock_shopify == "deny":
            must_be_update = True
        elif no_stock and stock_shopify == "continue":
            must_be_update = True
        if must_be_update is True:
            new_inventory_policy = "deny" if stock_shopify == "continue" else "continue"
            stock_change = ""
            if stock_shopify == "continue" and new_inventory_policy == "deny":
                stock_change = "deny_naturellement_bio"
            elif stock_shopify == "deny" and new_inventory_policy == "continue":
                stock_change = "allow_naturellement_bio"
            
            try:
                await update_variant_inventory(product["variants"][0]["id"], new_inventory_policy, session)
                await update_metafield_product(product["id"], formated_date, session)
                processed_products["treated"].append({
                    "id": product["id"],
                    "title": product["title"],
                    "status": "treated",
                    "stock_initial": map_inventory_policy(stock_shopify),
                    "stock_fournisseur": "en_stock_dp_nature" if has_stock else "rupture_dp_nature",
                    "stock_final": stock_change
                })
            except Exception as e:
                send_log(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "Naturellement Bio", __file__, 900, inspect.currentframe().f_code.co_name, f"Erreur lors de la mise à jour du produit {product['id']} : {e}", inspect.currentframe().f_lineno, "error")
                processed_products["failed"].append({
                    "id": product["id"],
                    "title": product["title"],
                    "status": "failed",
                    "stock_initial": "erreur",
                    "stock_fournisseur": "erreur",
                    "stock_final": "erreur"
                })
        elif must_be_update is False:
            processed_products["not_needed"].append({
                "id": product["id"],
                "title": product["title"],
                "status": "not_needed",
                "stock_initial": map_inventory_policy(stock_shopify),
                "stock_fournisseur": "en_stock_dp_nature" if has_stock else "rupture_dp_nature",
                "stock_final": map_inventory_policy(stock_shopify)
            })
        else:
            processed_products["failed"].append({
                "id": product["id"],
                "title": product["title"],
                "status": "failed",
                "stock_initial": "erreur",
                "stock_fournisseur": "erreur",
                "stock_final": "erreur"
            })

        print(i)
    with open("last_iteration.txt", "w") as f:
        f.write("0")
    save_processed_products_to_file()

############################################### Read End

############################################### Autre Start

def save_processed_products_to_file():
    today_date = datetime.now().strftime("%Y-%m-%d")
    file_path = "processed_products.txt"
    mode = "w"
    if os.path.exists(file_path):
        file_mod_time = datetime.fromtimestamp(os.path.getmtime(file_path))
        file_mod_date = file_mod_time.strftime("%Y-%m-%d")
        if file_mod_date == today_date:
            mode = "a"
    
    today_date_formatted = datetime.now().strftime("%d-%m-%Y")
    with open(file_path, mode, encoding="utf-8") as f:
        f.write(f"Processed Products Log - {today_date_formatted}\n")
        f.write("=" * 50 + "\n")
        f.write("ID_PRODUIT | TITRE_PRODUIT | ETAT_ACTUEL_NATURELLEMENTBIO | ETAT_SUR_DP_NATURE | ACTION_FAITE_PAR_ROBOT\n")
        f.write("-" * 100 + "\n")
        for status, products in processed_products.items():
            for product in products:
                product_id = product.get("id", "inconnu")
                title = product.get("title", "inconnu")
                stock_initial = product.get("stock_initial", "inconnu")
                stock_fournisseur = product.get("stock_fournisseur", "inconnu")
                stock_final = product.get("stock_final", "inconnu")
                line = f"{product_id} | {title} | {stock_initial} | {stock_fournisseur} | {stock_final}\n"
                f.write(line)

async def create_aiohttp_session():
    return aiohttp.ClientSession()
     
############################################### Autre End

############################################### Update Start

# requete api pour la mise a jour de l'inverntory policy ( vendre en cas de rupture de stock ou pas)
async def update_variant_inventory(variant_id, stock_value, session):
    print("fonction appelée")
    variant_data = {
        "variant": {
            "id": variant_id,
            "inventory_policy": stock_value
        }
    }

    url = f"https://{shop_link}/admin/api/{api_version}/variants/{variant_id}.json"
    headers = {
        "X-Shopify-Access-Token": access_token,
        "Content-Type": "application/json"
    }

    try:
        async with session.put(url, headers=headers, json=variant_data) as response:
            http_code = response.status
            response_text = await response.text()
            try:
                response_decoded = json.loads(response_text)
            except json.JSONDecodeError:
                response_decoded = response_text
            if http_code not in [200, 201]:
                send_log( datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "Naturellement Bio", __file__, http_code, inspect.currentframe().f_code.co_name, response_decoded, inspect.currentframe().f_lineno, "error")
    except Exception as e:
        send_log(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "Naturellement Bio", __file__, 900, inspect.currentframe().f_code.co_name, f"Exception aiohttp : {str(e)}", inspect.currentframe().f_lineno, "error")
            
# requete api pour la mise a jour de la metafield d'un produit avec la date du jour ( pour la mise a jour des produits)
async def update_metafield_product(product_id, value, session):
    global shop_link, access_token, api_version

    metafield_data = {
        "metafield": {
            "namespace": "custom",
            "key": "date_update_stock_huggii",
            "value": value,
            "type": "single_line_text_field"
        }
    }

    url = f"https://{shop_link}/admin/api/{api_version}/products/{product_id}/metafields.json"
    headers = {
        "X-Shopify-Access-Token": access_token,
        "Content-Type": "application/json"
    }

    try:
        async with session.post(url, headers=headers, json=metafield_data) as response:
            http_code = response.status
            response_text = await response.text()
            if http_code not in [200, 201]:
                send_log(datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"Naturellement Bio",__file__,http_code,inspect.currentframe().f_code.co_name,response_text,inspect.currentframe().f_lineno,"error")
    except Exception as e:
        send_log(datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"Naturellement Bio",__file__,900,inspect.currentframe().f_code.co_name,f"Exception aiohttp : {str(e)}",inspect.currentframe().f_lineno,"error")
    await asyncio.sleep(1)


############################################### Update End

############################################### Mailer Start

def send_mail():
    smtp_server = "smtp.gmail.com"
    smtp_port = 465
    username = "contact@huggii.com"
    password = "qytl gclj kicv eqcl"
    file_path = "processed_products.txt"

    # recipients = ["ethancro31@gmail.com", "ethan@huggii.fr"]
    recipients = ["ethancro31@gmail.com", "ethan@huggii.fr", "ecomcordo@gmail.com"]

    msg = MIMEMultipart()
    msg['From'] = username
    msg['To'] = ", ".join(recipients)
    msg['Subject'] = "Fichier traité - Naturellement Bio"

    body = "Veuillez trouver en pièce jointe le fichier des produits traités."
    msg.attach(MIMEText(body, 'plain'))

    try:
        with open(file_path, "rb") as attachment:
            part = MIMEBase("application", "octet-stream")
            part.set_payload(attachment.read())
        encoders.encode_base64(part)
        part.add_header(
            "Content-Disposition",
            f"attachment; filename={os.path.basename(file_path)}",
        )
        msg.attach(part)
    except FileNotFoundError:
        print(f"⚠️ Le fichier {file_path} est introuvable. Aucun fichier joint.")

    with smtplib.SMTP_SSL(smtp_server, smtp_port) as server:
        server.login(username, password)
        server.send_message(msg)

    print("Mail envoyé avec succès à plusieurs destinataires ✔️")

############################################### Mailer End

############################################### Main Start

def load_product_ids_from_csv(file_path):
    product_ids = []

    try:
        with open(file_path, mode='r', encoding='utf-8') as csv_file:
            csv_reader = csv.DictReader(csv_file, delimiter=';')

            for row in csv_reader:
                product_ids.append(int(row["ID"]))
    except FileNotFoundError:
        print(f"⚠️ Le fichier {file_path} est introuvable.")
    except KeyError:
        print(f"⚠️ La colonne 'ID' est introuvable dans le fichier CSV.")
    except Exception as e:
        print(f"⚠️ Une erreur est survenue lors de la lecture du fichier CSV : {e}")

    return product_ids

# La fonction principale du script :
# Récupère tous les produits de la boutique Shopify via une API asynchrone.
# Se connecte à la plateforme Dietpro pour établir une session.
# Initialise un navigateur Selenium (en mode headless) pour interagir avec DP Nature.
# Vérifie si la connexion à DP Nature est réussie, sinon envoie une erreur dans les logs.
# Attend un délai aléatoire avant d'appeler la fonction `read_product` pour traiter les produits.
# Gère les exceptions en envoyant des logs.
# Ferme proprement la session et le navigateur à la fin, en enregistrant la fin du script dans les logs.   
async def main():
    all_products = await get_all_products()
    driver = initialize_driver()
    session = await create_aiohttp_session()
    try:
        login_to_dpnature(driver, email, password)
        if not is_logged_in(driver):
            send_log(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "Naturellement Bio", __file__, 900, inspect.currentframe().f_code.co_name, "Erreur de connexion à dpnature", inspect.currentframe().f_lineno, "error")
            return
        wait_time = random.uniform(5, 15)
        time.sleep(wait_time)
        try:
            await read_product(driver, all_products, session, max_duration=3300)
        except NoSuchElementException as e:
            send_log(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "Naturellement Bio", __file__, 901, "read_product", f"Élément introuvable : {e}", inspect.currentframe().f_lineno, "error")
        except TimeoutError as e:
            send_log(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "Naturellement Bio", __file__, 902, "read_product", f"Temps d’attente dépassé : {e}", inspect.currentframe().f_lineno, "error")
        except Exception as e:
            send_log(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "Naturellement Bio", __file__, 903, "read_product", f"Erreur inconnue : {e}", inspect.currentframe().f_lineno, "error")
    except WebDriverException as e:
        send_log(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "Naturellement Bio", __file__, 904, "main", f"Erreur WebDriver : {e}", inspect.currentframe().f_lineno, "error")
    except Exception as e:
        send_log(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "Naturellement Bio", __file__, 900, "main", str(e), inspect.currentframe().f_lineno, "error")
    finally:
        await session.close()
        wait_random(1, 2, "avant fermeture navigateur")
        driver.quit()
        send_mail()
        send_log(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "Naturellement Bio", __file__, 200, inspect.currentframe().f_code.co_name, "Fin du script stock", inspect.currentframe().f_lineno, "success")
asyncio.run(main())
sys.exit(0)
