import csv
import locale
import os
import sys
import weakref
from glob import glob

from lib.db import Recipes, Users, db
from PyQt5.QtCore import QTimer, pyqtSignal
from PyQt5.QtGui import QKeySequence
from PyQt5.QtWidgets import QFileDialog, QMessageBox, QShortcut
import shutil

from lib.helpers.recipe_manager import export_recipes, import_recipes,recipe_manager_signals
from lib.helpers.step import Step
from ui.crud import Crud, Json_External_Dialog_Editor_Cell_Widget
from ui.helpers import replace_widget
from ui.recipe_spec_and_step_editor import Recipe_Spec_And_Step_Editor
from ui.widget import Widget

from datetime import datetime

class Noner:
    def __getitem__(self, key):
        return None


noner = Noner()


class Recipe_Selection(Widget):
    ok = pyqtSignal(Recipes)
    POSIX_BASE_DIR = "/home/gg/PycharmProjects/st-ten-1/config/csv_import/manual_csv_export"
    WINDOWS_BASE_DIR = r"C:\Users\gg\PycharmProjects\st-ten-1\config\csv_import\manual_csv_export"

    def __init__(self, config, unsupported_steps=None):
        global noner
        super().__init__()
        self.config = config
        self.second_leak_test_enabled = self.config["hardware_config"]["second_leak_test"] == "present"
        self.defaults = self.config.get("recipes_defaults", noner)
        self.unsupported_steps = unsupported_steps
        session = Users.get_session()
        if session.is_admin:
            readonly = False
            crud_aliases = {
                "name": "Ricetta",
                "client": "Cliente",
                "part_number": "N° disegno",
                "spec": "Specifica",
                "description": "Descrizione",
            }
            filters = None
        else:
            readonly = True
            crud_aliases = {
                "name": "Ricetta",
                "client": "Cliente",
                "part_number": "N° disegno",
                "spec": "Specifica",
                "description": "Descrizione",
            }
            filters = {"archived": False}
        step_defaults = self.read_steps(self.config.get("recipes_defaults", noner), noner)
        if self.config["label_printer"]["risoluzione"] == "300":
            custom_label_folder = f"config/label_templates/{str(self.config.machine_id)}/300/"
            standard_label_folder = f"config/label_templates/300/"
        elif self.config["label_printer"]["risoluzione"] == "203":
            custom_label_folder = f"config/label_templates/{str(self.config.machine_id)}/203/"
            standard_label_folder = f"config/label_templates/203/"

        if os.path.exists(custom_label_folder):
            label_folder = custom_label_folder
        else:
            label_folder = standard_label_folder

        step_defaults.update({
            "vision": {
                # "recipe": sorted(glob("*.ini", root_dir="./config/vision/recipes/")), # only in python3.10
                "recipe": sorted(map(os.path.basename, glob("./config/vision/recipes/*.ini"))),
            },
            "print": {
                # "template": sorted(glob("*.prn", root_dir="./config/label_templates/")), # only in python3.10
                "template": sorted(map(os.path.basename, glob(f"{label_folder}*.prn"))),
            },
        }),
        self.crud = Crud(
            "recipes",
            display_name="SELEZIONE RICETTA",
            readonly=readonly,
            select=list(crud_aliases.keys()),
            filters=filters,
            fields_aliases=crud_aliases,
            autocomplete={
                "name": self.config.get("recipes_defaults", noner)["codice_ricetta"],
                "client": self.config.get("recipes_defaults", noner)["cliente"],
                "part_number": self.config.get("recipes_defaults", noner)["part_number"],
                "spec": {
                    "count": len(self.config.get("recipes_defaults", noner)["dimensione_lotto_abilitata"]) and "count" not in self.unsupported_steps,
                    "connector": len(self.config.get("recipes_defaults", noner)["verifica_connettore_abilitata"]) and "connector" not in self.unsupported_steps,
                    "barcodes": len(
                        self.config.get("recipes_defaults", noner)["verifica_codice_a_barre_abilitata"]) and "barcodes" not in self.unsupported_steps,
                    "resistance": len(
                        self.config.get("recipes_defaults", noner)["verifica_resistenza_connettore_abilitata"]) and "resistance" not in self.unsupported_steps,
                    "screws": len(self.config.get("recipes_defaults", noner)["avvitatura_abilitata"]) and "screws" not in self.unsupported_steps,
                    "instruction": len(self.config.get("recipes_defaults", noner)["istruzione_abilitata"]) and "instruction" not in self.unsupported_steps,
                    "instruction_extra": len(self.config.get("recipes_defaults", noner)["istruzione_abilitata_extra"]) and "instruction_extra" not in self.unsupported_steps,
                    "pipe_cutter": len(self.config.get("recipes_defaults", noner)["tagliatubi_abilitata"]) and "pipe_cutter" not in self.unsupported_steps,
                    "vision": len(self.config.get("recipes_defaults", noner)["test_visione_abilitato"]) and "vision" not in self.unsupported_steps,
                    "leak_1": len(self.config.get("recipes_defaults", noner)["prova_tenuta_abilitata"]) and "leak_1" not in self.unsupported_steps,
                    "leak_2": len(self.config.get("recipes_defaults", noner)["prova_tenuta_abilitata_2"]) and "leak_2" not in self.unsupported_steps,
                    "print": len(self.config.get("recipes_defaults", noner)["stampa_etichetta_abilitata"]) and "print" not in self.unsupported_steps,
                    "step_editors": step_defaults,
                },
                "description": self.config.get("recipes_defaults", noner)["descrizione"],
                "archived": False,
            },
            sort={"name": True},
            widget_classes={"spec": lambda *args, **kwargs: Json_External_Dialog_Editor_Cell_Widget(
                Recipe_Spec_And_Step_Editor,
                *args,
                **kwargs,
                unsupported_steps=self.unsupported_steps
            ), },
            pagination=25,
        )
        replace_widget(self, "crud_w", self.crud)
        self.crud_modified = None
        self.selected = None
        self.select_b.setEnabled(False)
        self.select_b.clicked.connect(self.select)
        QShortcut(QKeySequence("Return"), self).activated.connect(self.select_b.click)
        QShortcut(QKeySequence("Enter"), self).activated.connect(self.select_b.click)
        self.crud.modified.connect(self.check_modified)
        self.crud.selected.connect(self.check_selected)
        self.crud.emit()
        self.crud.db_tw.setColumnWidth(0, 200)
        self.crud.db_tw.setColumnWidth(1, 200)
        self.crud.db_tw.setColumnWidth(2, 200)
        self.crud.db_tw.setColumnWidth(3, 200)
        self.crud.db_tw.setColumnWidth(4, 200)
        self.crud.db_tw.setColumnWidth(5, 400)
        if session.is_admin:
            self.import_b.clicked.connect(lambda checked, selfi=weakref.ref(self): selfi().import_recipes())
            self.export_b.clicked.connect(lambda checked, selfi=weakref.ref(self): selfi().export_recipes())
            self.delete_all_b.clicked.connect(lambda checked, selfi=weakref.ref(self): selfi().delete_recipes())
        else:
            self.import_b.setVisible(False)
            self.export_b.setVisible(False)
            self.delete_all_b.setVisible(False)

        # TESTING
        if self.config.auto_select is not None:
            recipe = self.config.auto_select
            cn = self.crud.select_index["name"]
            self.crud.db_tw.clearSelection()
            for rn in range(1, self.crud.db_tw.rowCount()):
                if self.crud.db_tw.cellWidget(rn, cn).text() == recipe:
                    selection = self.crud.db_tw.model().index(rn, cn)
                    self.crud.db_tw.setCurrentIndex(selection)
                    break
            self.test_timer = QTimer()
            self.test_timer.setSingleShot(True)
            self.test_timer.timeout.connect(self.select_b.clicked.emit)
            self.test_timer.start(500)
        # /TESTING

        recipe_manager_signals.recipes_imported.connect(self.crud.refresh)

    def check_modified(self, modified):
        self.crud_modified = modified
        self.check(self.crud_modified, self.selected)

    def check_selected(self, selected=None):
        if selected is not None and len(selected) == 1:
            selected = selected[0] - 1  # - 1 because rn starts from 1 (filters line)
            if selected >= 0 and selected < len(self.crud.data_index):
                selected = self.crud.data_index[selected]
                if selected is not None:
                    self.selected = selected
                else:
                    self.selected = None
        else:
            self.selected = None
        self.check(self.crud_modified, self.selected)

    def check(self, modified, selected):
        self.select_b.setEnabled(modified is False and selected is not None)

    def refresh(self):
        """Update the UI based on current admin privileges"""
        session = Users.get_session()
        # Check if user has admin privileges (either permanent or temporary)
        # Use session.is_admin instead of checking roles directly to be consistent with user.py
        has_admin = session.is_admin

        # Update button visibility
        self.import_b.setVisible(has_admin)
        self.export_b.setVisible(has_admin)
        self.delete_all_b.setVisible(has_admin)

        # Connect or disconnect button handlers
        if has_admin:
            # Disconnect existing handlers to avoid multiple connections
            try:
                self.import_b.clicked.disconnect()
            except:
                pass
            try:
                self.export_b.clicked.disconnect()
            except:
                pass
            try:
                self.delete_all_b.clicked.disconnect()
            except:
                pass

            # Connect handlers
            self.import_b.clicked.connect(lambda checked, selfi=weakref.ref(self): selfi().import_recipes())
            self.export_b.clicked.connect(lambda checked, selfi=weakref.ref(self): selfi().export_recipes())
            self.delete_all_b.clicked.connect(lambda checked, selfi=weakref.ref(self): selfi().delete_recipes())

        # Update CRUD readonly status
        if has_admin:
            self.crud.set_readonly(False)
        else:
            self.crud.set_readonly(True)

    def select(self):
        if self.selected is not None:
            self.ok.emit(self.crud.db.table_model.get_by_id(self.selected))

    def get_def(self, dict, key):
        val = dict.get(key, self.defaults[key])
        return val if val != "" else self.defaults[key]

    # READ RECIPE STEPS FROM CSV ROW
    def read_steps(self, row, defaults=None):
        if defaults is None:
            global noner
            defaults = self.config.get("recipes_defaults", noner)
        barcode_serial_field = self.config.get("recipe", {}).get("barcode_serial_field", "codice_a_barre").strip()
        warning_image_field = self.config.get("recipe", {}).get("warning_image_field", "warning_img").strip()
        decsep = locale.localeconv()["decimal_point"]
        rcsv = row.get("r nominale", defaults["r nominale"]).replace(" ", "").replace(",", decsep).replace("Ω", "").replace("?", "")
        if rcsv == "":
            rcsv = "999"
        print_template_field = self.config.get("recipe", {}).get("label_template_field", "modello_etichetta").strip()
        return {
            "count": {
                "amount": row.get("dimensione_lotto", defaults["dimensione_lotto"]),
                "warning_img": row.get(warning_image_field, defaults["warning_img"]),
                "require_discard_piece": row.get("richiedi_inserimento_scarto", defaults["richiedi_inserimento_scarto"])
            },
            "connector": {
                "connector": row.get("connettore", defaults["connettore"]),
            },
            "barcodes": {
                "serial": row.get(barcode_serial_field, defaults["codice_a_barre"]),
                "n_pieces": row.get("n_componenti") or defaults["n_componenti"],
                "barcode_input_2": row.get("barcode_input_2", "-"),
                "barcode_input_3": row.get("barcode_input_3", "-"),
                "barcode_input_4": row.get("barcode_input_4", "-"),
                "barcode_input_5": row.get("barcode_input_5", "-"),
            },
            "resistance": {
                "scale": locale.atof(row.get("scala_resistenza", defaults["scala_resistenza"])),
                "expected": locale.atof(rcsv),
                "tolerance_pos": locale.atof(self.get_def(row, "tolleranza_resistenza_pos")),
                "tolerance_neg": locale.atof(self.get_def(row, "tolleranza_resistenza_neg")),
            },
            "screws": {
                "quantity": row.get("viti", defaults["viti"])
            },
            "instruction": {},
            "pipe_cutter": {
                "length": row.get("lunghezza_corrugato", defaults["lunghezza_corrugato"]),
                "diameter": row.get("diametro", defaults["diametro"]),
            },
            "leak_1": {
                "pre_filling_time": int(float(row.get("tempo_pre_riempimento", defaults["tempo_pre_riempimento"]))),
                "pre_filling_pressure": int(float(row.get("pressione_pre_riempimento", defaults["pressione_pre_riempimento"]))),
                "filling_time": int(float(row.get("tempo_riempimento", defaults["tempo_riempimento"]))),
                "settling_time": int(float(self.get_def(row, "tempo_assestamento"))),
                "settling_pressure_min_percent": int(float(
                    row.get("percentuale_minima_pressione_assestamento", defaults["percentuale_minima_pressione_assestamento"]))),
                "settling_pressure_max_percent": int(float(
                    row.get("percentuale_massima_pressione_assestamento", defaults["percentuale_massima_pressione_assestamento"]))),
                "test_time": int(float(row.get("tempo_di_test", defaults["tempo_di_test"]))),
                "test_pressure_qneg": int(float(row.get("pressione_di_test_delta_minimo", defaults["pressione_di_test_delta_minimo"]))),
                "test_pressure": int(float(row.get("pressione_di_test", defaults["pressione_di_test"]))),
                "test_pressure_qpos": int(float(row.get("pressione_di_test_delta_massimo", defaults["pressione_di_test_delta_massimo"]))),
                "flush_time": int(float(row.get("tempo_svuotamento", defaults["tempo_svuotamento"]))),
                "flush_pressure": int(float(row.get("pressione_svuotamento", defaults["pressione_svuotamento"]))),
                "chan_sel": int(float(row.get("canale_di_prova", defaults["canale_di_prova"]))),
                "ext_flush_time": int(float(row.get("tempo_svuotamento_esterno", defaults["tempo_svuotamento_esterno"]))),
                "ext_blow_time": int(float(row.get("tempo_soffiaggio_esterno", defaults["tempo_soffiaggio_esterno"]))),
                "pid_pressure_correction" : int(float(row.get("pid_pressure_correction", defaults["pid_pressure_correction"]))),
            },
            "leak_2": {
                "pre_filling_time": int(float(row.get("tempo_pre_riempimento_2", defaults["tempo_pre_riempimento_2"]))),
                "pre_filling_pressure": int(float(row.get("pressione_pre_riempimento_2", defaults["pressione_pre_riempimento_2"]))),
                "filling_time": int(float(row.get("tempo_riempimento_2", defaults["tempo_riempimento_2"]))),
                "settling_time": int(float(row.get("tempo_assestamento_2", defaults["tempo_assestamento_2"]))),
                "settling_pressure_min_percent": int(float(
                    row.get("percentuale_minima_pressione_assestamento_2", defaults["percentuale_minima_pressione_assestamento_2"]))),
                "settling_pressure_max_percent": int(float(
                    row.get("percentuale_massima_pressione_assestamento_2", defaults["percentuale_massima_pressione_assestamento_2"]))),
                "test_time": int(float(row.get("tempo_di_test_2", defaults["tempo_di_test_2"]))),
                "test_pressure_qneg": int(float(row.get("pressione_di_test_delta_minimo_2", defaults["pressione_di_test_delta_minimo_2"]))),
                "test_pressure": int(float(row.get("pressione_di_test_2", defaults["pressione_di_test_2"]))),
                "test_pressure_qpos": int(float(row.get("pressione_di_test_delta_massimo_2", defaults["pressione_di_test_delta_massimo_2"]))),
                "flush_time": int(float(row.get("tempo_svuotamento_2", defaults["tempo_svuotamento_2"]))),
                "flush_pressure": int(float(row.get("pressione_svuotamento_2", defaults["pressione_svuotamento_2"]))),
                "chan_sel": int(float(row.get("canale_di_prova_2", defaults["canale_di_prova_2"]))),
                "ext_flush_time": int(float(row.get("tempo_svuotamento_esterno_2", defaults["tempo_svuotamento_esterno"]))),
                "ext_blow_time": int(float(row.get("tempo_soffiaggio_esterno_2", defaults["tempo_soffiaggio_esterno"]))),
                "pid_pressure_correction": int(float(row.get("pid_pressure_correction_2", defaults["pid_pressure_correction_2"]))),

            },
            "vision": {
                "recipe": row.get("ricetta_visione", defaults["ricetta_visione"]),
            },
            "print": {
                "template": row.get(print_template_field, defaults["modello_etichetta"]),
                "labeltxt_1": row.get("testo_etich_1", ""),
                "labeltxt_2": row.get("testo_etich_2", ""),
                "labeltxt_3": row.get("testo_etich_3", ""),
                "labeltxt_4": row.get("testo_etich_4", ""),
                "labeltxt_5": row.get("barcode_input_finelinea", ""),
                "extra_label": row.get("etichette_supplementari", ""),

            },
        }

    # IMPORT RECIPES FROM CSV FILE TO DATABASE
    def import_recipes(self, csv_path=None, defaults=None):
        import_recipes(
            config=self.config,
            csv_path=csv_path,
            defaults=defaults,
            unsupported_steps=self.unsupported_steps,
            logger=self.log,
        )
        self.crud.refresh()

    # EXPORT RECIPES TABLE TO CSV FILE
    def export_recipes(self, csv_path=None, base_dir=None):
        """
        Export recipes to a CSV file. Opens a dialog for saving if no path is provided.
        """
        base_dir = base_dir or self._get_base_dir()  # Use base directory or default

        if csv_path is None:
            # Prepare initial directory and filename
            initial_path = self._prepare_initial_file_path(base_dir)
            # Open Save File dialog with the default path
            csv_path, _ = QFileDialog.getSaveFileName(
                self,
                "Esportazione ricette",
                initial_path,
                "CSV data (*.csv);;All Files (*)",
            )
        if not csv_path:  # If no file selected, exit
            return

        # Ensure filename ends with .csv
        if not csv_path.lower().endswith(".csv"):
            csv_path += ".csv"

        # Create directories if needed
        output_dir = os.path.dirname(csv_path)
        try:
            os.makedirs(output_dir, exist_ok=True)
        except Exception as e:
            self.log.error(f"Error creating directories for export: {e}")
            return

        recipe_name_field = self.config.get("recipe", {}).get("recipe_name_field", "codice_ricetta").strip()
        barcode_enable_field = self.config.get("recipe", {}).get("barcode_enable_field", "verifica_codice_a_barre_abilitata").strip()
        barcode_serial_field = self.config.get("recipe", {}).get("barcode_serial_field", "codice_a_barre").strip()
        print_template_field = self.config.get("recipe", {}).get("label_template_field", "modello_etichetta").strip()
        data = []
        fieldnames = [
            recipe_name_field,
            "cliente",
            "part_number",
            "dimensione_lotto_abilitata",
            "dimensione_lotto",
            "verifica_connettore_abilitata",
            "connettore",
            barcode_enable_field,
            barcode_serial_field,
            "verifica_resistenza_connettore_abilitata",
            "scala_resistenza",
            "r nominale",
            "tolleranza_resistenza_pos",
            "tolleranza_resistenza_neg",
            "avvitatura_abilitata",
            "viti",
            "prova_tenuta_abilitata",
            "tempo_pre_riempimento",
            "pressione_pre_riempimento",
            "tempo_riempimento",
            "tempo_assestamento",
            "percentuale_minima_pressione_assestamento",
            "percentuale_massima_pressione_assestamento",
            "tempo_di_test",
            "pressione_di_test_delta_minimo",
            "pressione_di_test",
            "pressione_di_test_delta_massimo",
            "tempo_svuotamento",
            "pressione_svuotamento",
            "prova_tenuta_abilitata_2",
            "tempo_pre_riempimento_2",
            "pressione_pre_riempimento_2",
            "tempo_riempimento_2",
            "tempo_assestamento_2",
            "percentuale_minima_pressione_assestamento_2",
            "percentuale_massima_pressione_assestamento_2",
            "tempo_di_test_2",
            "pressione_di_test_delta_minimo_2",
            "pressione_di_test_2",
            "pressione_di_test_delta_massimo_2",
            "tempo_svuotamento_2",
            "pressione_svuotamento_2",
            "test_visione_abilitato",
            "ricetta_visione",
            "stampa_etichetta_abilitata",
            print_template_field,
            "labeltxt_1",
            "labeltxt_2",
            "labeltxt_3",
            "labeltxt_4",
            "labeltxt_5",
        ]
        for recipe in list(Recipes.select()):
            steps = recipe.get_steps_map()
            exportable = {
                recipe_name_field: recipe.name,
                "cliente": recipe.client,
                "part_number": recipe.part_number,
                "verifica_connettore_abilitata": "x" if recipe.spec.get("connector",False) else "",
                "connettore": steps.get("connector",Step()).spec.get("connector",""),
                barcode_enable_field: "x" if recipe.spec.get("barcodes",False) in ("x",True,"true","True") else "",
                barcode_serial_field: steps.get("barcodes",Step()).spec.get("serial",""),
                "verifica_resistenza_connettore_abilitata": "x" if recipe.spec.get("resistance",False) else "",
                "scala_resistenza": steps.get("resistance",Step()).spec.get("scale",""),
                "r nominale": steps.get("resistance",Step()).spec.get("expected",""),
                "tolleranza_resistenza_pos": steps.get("resistance",Step()).spec.get("tolerance_pos",""),
                "tolleranza_resistenza_neg": steps.get("resistance",Step()).spec.get("tolerance_neg",""),
                # "avvitatura_abilitata": "x" if recipe.spec.get("screws",False) else "",
                # "viti": steps.get("screws",Step()).spec.get("quantity",""),
                "prova_tenuta_abilitata": "x" if recipe.spec.get("leak_1",False) else "",
                "tempo_pre_riempimento": steps.get("leak_1",Step()).spec.get("pre_filling_time",""),
                "pressione_pre_riempimento": steps.get("leak_1",Step()).spec.get("pre_filling_pressure",""),
                "tempo_riempimento": steps.get("leak_1",Step()).spec.get("filling_time",""),
                "tempo_assestamento": steps.get("leak_1",Step()).spec.get("settling_time",""),
                "percentuale_minima_pressione_assestamento": steps.get("leak_1",Step()).spec.get("settling_pressure_min_percent",""),
                "percentuale_massima_pressione_assestamento": steps.get("leak_1",Step()).spec.get("settling_pressure_max_percent",""),
                "tempo_di_test": steps.get("leak_1",Step()).spec.get("test_time",""),
                "pressione_di_test_delta_minimo": steps.get("leak_1",Step()).spec.get("test_pressure_qneg",""),
                "pressione_di_test": steps.get("leak_1",Step()).spec.get("test_pressure",""),
                "pressione_di_test_delta_massimo": steps.get("leak_1",Step()).spec.get("test_pressure_qpos",""),
                "tempo_svuotamento": steps.get("leak_1",Step()).spec.get("flush_time",""),
                "pressione_svuotamento": steps.get("leak_1",Step()).spec.get("flush_pressure",""),
                "prova_tenuta_abilitata_2": "x" if recipe.spec.get("leak_2", False) in ("x",True,"true","True") else "",
                "tempo_pre_riempimento_2": steps.get("leak_2",Step()).spec.get("pre_filling_time",""),
                "pressione_pre_riempimento_2": steps.get("leak_2",Step()).spec.get("pre_filling_pressure",""),
                "tempo_riempimento_2": steps.get("leak_2",Step()).spec.get("filling_time",""),
                "tempo_assestamento_2": steps.get("leak_2",Step()).spec.get("settling_time",""),
                "percentuale_minima_pressione_assestamento_2": steps.get("leak_2",Step()).spec.get("settling_pressure_min_percent",""),
                "percentuale_massima_pressione_assestamento_2": steps.get("leak_2",Step()).spec.get("settling_pressure_max_percent",""),
                "tempo_di_test_2": steps.get("leak_2",Step()).spec.get("test_time",""),
                "pressione_di_test_delta_minimo_2": steps.get("leak_2",Step()).spec.get("test_pressure_qneg",""),
                "pressione_di_test_2": steps.get("leak_2",Step()).spec.get("test_pressure",""),
                "pressione_di_test_delta_massimo_2": steps.get("leak_2",Step()).spec.get("test_pressure_qpos",""),
                "tempo_svuotamento_2": steps.get("leak_2",Step()).spec.get("flush_time",""),
                "pressione_svuotamento_2": steps.get("leak_2",Step()).spec.get("flush_pressure",""),
                "test_visione_abilitato": recipe.spec.get("vision",""),
                "ricetta_visione": steps.get("vision",Step()).spec.get("recipe",""),
                "stampa_etichetta_abilitata": "x" if recipe.spec.get("print",False) else "",
                print_template_field: steps.get("print",Step()).spec.get("template",""),
                "labeltxt_1": steps.get("print",Step()).spec.get("labeltxt_1",""),
                "labeltxt_2": steps.get("print", Step()).spec.get("labeltxt_2", ""),
                "labeltxt_3": steps.get("print", Step()).spec.get("labeltxt_3", ""),
                "labeltxt_4": steps.get("print", Step()).spec.get("labeltxt_4", ""),
                "labeltxt_5": steps.get("print", Step()).spec.get("labeltxt_5", ""),
            }
            data.append(exportable)
        if len(data):
            self.log.info(f"recipes: exporting recipes to {csv_path}")
            with open(csv_path, "w", newline="") as f:
                w = csv.DictWriter(f, fieldnames, extrasaction="ignore")
                w.writeheader()
                w.writerows(data)
            self.log.info(f"recipes: exported {len(data)} rows.")

    def delete_recipes(self):
        ret = QMessageBox.warning(
            None,
            "Attenzione si sta cercando di cancellare tutte le ricette!",
            "Si è sicuri di voler eliminare tutte le ricette?\nQuesta operazione non può essere annullata!",
            buttons=QMessageBox.Ok | QMessageBox.Cancel,
            defaultButton=QMessageBox.Cancel
        )
        if ret == QMessageBox.Ok:
            Recipes.delete().execute()
            self.crud.refresh()

    def _prepare_initial_file_path(self, base_dir):
        """
        Prepare the initial file path for the QFileDialog.
        Includes a directory and default filename.
        """
        timestr = datetime.now().strftime("%d-%m-%Y")
        default_filename = f"{self.config.machine_id}_RECIPES_{timestr}.csv"
        return os.path.join(base_dir, default_filename)

    def _get_base_dir(self):
        """
        Returns the base directory for CSV exports based on the operating system.
        :raises: Exception if the OS is unsupported.
        """
        if os.name == "posix":  # For Linux/Unix-based systems
            return self.POSIX_BASE_DIR
        elif os.name == "nt":  # For Windows
            return self.WINDOWS_BASE_DIR
        else:
            raise Exception("Unsupported operating system!")
