import os
import subprocess
import json
import uuid
import configparser
from app.utils.conf_parse import ConfParse

class CuraEngine:
    def __init__(self):
        self.name = "cura"
        self.display_name = "UltiMaker Cura"
        self.is_available = self._check_available()
        
    def _check_available(self):
        try:
            # check if CuraEngine is available in PATH
            result = subprocess.run(["CuraEngine", "help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            return result.returncode == 0 or b"Usage:" in result.stdout or b"Usage:" in result.stderr
        except (FileNotFoundError, OSError):
            return False
        self.display_name = "UltiMaker Cura"
        self.is_available = self._check_available()
        
    def _check_available(self):
        try:
            # check if CuraEngine is available in PATH
            result = subprocess.run(["CuraEngine", "help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            return result.returncode == 0 or b"Usage:" in result.stdout or b"Usage:" in result.stderr
        except (FileNotFoundError, OSError):
            return False

    def slice(self, app, stl_filepath, gcode_filepath, **kwargs):
        """
        Slices via CuraEngine.
        Returns (success_bool, error_msg_if_any)
        """
        quality_preset = kwargs.get('quality_preset')
        infill_density = kwargs.get('infill_density')
        support_enable = kwargs.get('support_enable')
        support_pattern = kwargs.get('support_pattern')
        
        tmp_def_path = None
        try:
            base_config_path = os.path.abspath(os.path.join(app.root_path, '..', 'print_config'))
            print_config_path = os.path.join(base_config_path, 'cura_engine')
            printers_path = os.path.join(print_config_path, 'printers')
            extruders_path = os.path.join(print_config_path, 'extruders')
            materials_path = os.path.join(print_config_path, 'materials')
            presets_path = os.path.join(print_config_path, 'quality')
            variants_path = os.path.join(print_config_path, 'variants')
            
            env = os.environ.copy()
            env["CURA_ENGINE_SEARCH_PATH"] = f"{printers_path}:{extruders_path}:{materials_path}:{presets_path}:{variants_path}"
            
            def_files = [
                os.path.join(printers_path, "fdmprinter.def.json"),
                os.path.join(printers_path, "fdmextruder.def.json"),
                os.path.join(printers_path, "creality_base.def.json"),
                os.path.join(printers_path, "creality_ender3v3se.def.json")
            ]
            
            inst_files_list = []
            quality_type = None
            preset_path = None
            
            if quality_preset:
                config = configparser.ConfigParser()
                preset_path = os.path.join(presets_path, 'creality', 'presets', quality_preset)
                if os.path.exists(preset_path):
                    config.read(preset_path)
                    material_type_from_preset = config.get('metadata', 'material', fallback=None)
                    variant_type = config.get('metadata', 'variant', fallback=None)
                    quality_type = config.get('metadata', 'quality_type', fallback=None)
                    
                    # Use explicit material if provided, otherwise fallback to preset's material
                    material_type = kwargs.get('material_preset') or material_type_from_preset
                    
                    if material_type:
                        m_path = os.path.join(materials_path, f"{material_type}.inst.cfg")
                        if not os.path.exists(m_path) and kwargs.get('material_preset'):
                            m_path = os.path.join(materials_path, f"{kwargs.get('material_preset')}")
                        if os.path.exists(m_path): inst_files_list.append(m_path)
                    if variant_type:
                        variant_d = variant_type.split("mm")[0]
                        v_path = os.path.join(variants_path, "creality", f"creality_ender3v3se_{variant_d}.inst.cfg")
                        if os.path.exists(v_path): inst_files_list.append(v_path)
                        
            if support_pattern == 'tree':
                t_path = os.path.join(print_config_path, 'supports', 'tree.inst.cfg')
                if os.path.exists(t_path): inst_files_list.append(t_path)
            elif support_pattern and support_pattern != 'false':
                n_path = os.path.join(print_config_path, 'supports', 'normal.inst.cfg')
                if os.path.exists(n_path): inst_files_list.append(n_path)
                
            if quality_preset and quality_type:
                g_path = os.path.join(presets_path, 'creality', 'globals', f"{quality_type}.inst.cfg")
                if os.path.exists(g_path): inst_files_list.append(g_path)
            
            if quality_preset and preset_path and os.path.exists(preset_path):
                inst_files_list.append(preset_path)
                
            p = ConfParse(def_files)
            settings_with_inst = p.add_inst_cfg(inst_files_list)
            
            if infill_density is not None:
                if "infill_sparse_density" not in settings_with_inst: settings_with_inst["infill_sparse_density"] = {}
                settings_with_inst["infill_sparse_density"]["value"] = str(infill_density)
                if "infill_line_distance" not in settings_with_inst: settings_with_inst["infill_line_distance"] = {}
                settings_with_inst["infill_line_distance"]["value"] = str(100 / int(infill_density)) if int(infill_density) > 0 else "9999"
                
            if support_enable is not None:
                if "support_enable" not in settings_with_inst: settings_with_inst["support_enable"] = {}
                settings_with_inst["support_enable"]["value"] = True if support_enable in ['true', 'buildplate'] else False
                if "support_type" not in settings_with_inst: settings_with_inst["support_type"] = {}
                settings_with_inst["support_type"]["value"] = "'buildplate'" if support_enable == 'buildplate' else "'everywhere'"
                
            if support_pattern == 'tree':
                if "support_structure" not in settings_with_inst: settings_with_inst["support_structure"] = {}
                settings_with_inst["support_structure"]["value"] = "'tree'"
            elif support_pattern and "support_pattern" in settings_with_inst and "options" in settings_with_inst["support_pattern"] and support_pattern in settings_with_inst["support_pattern"]["options"].keys():
                if "support_structure" not in settings_with_inst: settings_with_inst["support_structure"] = {}
                settings_with_inst["support_structure"]["value"] = "'normal'"
                if "support_pattern" not in settings_with_inst: settings_with_inst["support_pattern"] = {}
                settings_with_inst["support_pattern"]["value"] = f"'{support_pattern}'"

            res = p.parse_configs(settings_with_inst)
            
            override_dict = {}
            for k, v in res.items():
                if v.get("enabled", True):
                    val = v.get("value", None)
                    if val is not None:
                        override_dict[k] = {"value": val, "default_value": val}
                    elif "default_value" in v:
                        override_dict[k] = {"default_value": v["default_value"], "value": v["default_value"]}

            tmp_def_filename = f"tmp_{uuid.uuid4().hex}.def.json"
            tmp_def_path = os.path.join(app.config['UPLOAD_FOLDER'], tmp_def_filename)
            
            tmp_def_obj = {
                "version": 2,
                "name": "TempProfile",
                "inherits": "fdmprinter",
                "metadata": {
                    "visible": True,
                    "author": "System",
                    "manufacturer": "System",
                    "file_formats": "text/x-gcode",
                    "first_start_actions": ["MachineSettingsAction"],
                    "has_materials": True,
                    "has_variants": True,
                    "has_machine_quality": True,
                    "variants_name": "Nozzle Size",
                    "preferred_variant_name": "0.4mm Nozzle",
                    "preferred_quality_type": "standard",
                    "preferred_material": "generic_pla",
                },
                "overrides": override_dict
            }

            pretty_json = json.dumps(tmp_def_obj, indent=4)
            
            with open(tmp_def_path, "w") as f:
                f.write(pretty_json)

            command = [
                "CuraEngine", "slice",
                "-j", tmp_def_path,
                "-l", stl_filepath,
                "-o", gcode_filepath
            ]
            
            app.logger.info(f"Running command: {' '.join(command)}")
            process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
            stdout, stderr = process.communicate()

            if process.returncode == 0:
                return True, None
            else:
                err_msg = stderr.decode() if stderr else "Unknown CuraEngine error"
                app.logger.error(f"CuraEngine Error: {err_msg}")
                return False, err_msg
                
        except Exception as e:
            app.logger.error(f"CuraEngine Exception: {e}")
            return False, str(e)
            
        finally:
            if tmp_def_path and os.path.exists(tmp_def_path):
                try:
                    os.remove(tmp_def_path)
                except Exception as e:
                    app.logger.error(f"Failed to delete temp JSON config {tmp_def_path}: {e}")

    def get_quality_presets(self, app):
        try:
            path = os.path.join(app.root_path, '..', 'print_config', 'cura_engine', 'quality', 'creality', 'presets')
            if not os.path.exists(path): return []
            files = [f for f in os.listdir(path) if f.endswith('.inst.cfg')]
            presets = []
            for f in files:
                presets.append({'id': f, 'name': f.replace('.inst.cfg', '')})
            presets.sort(key=lambda x: x['name'])
            return presets
        except:
            return []

    def get_support_patterns(self, app):
        return [
            {'id': 'tree', 'name': 'Tree'},
            {'id': 'lines', 'name': 'Lines'},
            {'id': 'grid', 'name': 'Grid'},
            {'id': 'triangles', 'name': 'Triangles'},
            {'id': 'concentric', 'name': 'Concentric'},
            {'id': 'zigzag', 'name': 'Zig Zag'},
            {'id': 'cross', 'name': 'Cross'},
            {'id': 'gyroid', 'name': 'Gyroid'},
            {'id': 'honeycomb', 'name': 'Honeycomb'},
            {'id': 'octagon', 'name': 'Octagon'}
        ]

    def get_materials(self, app):
        try:
            path = os.path.join(app.root_path, '..', 'print_config', 'cura_engine', 'materials')
            if not os.path.exists(path): return []
            files = [f for f in os.listdir(path) if f.endswith('.inst.cfg')]
            materials = []
            for f in files:
                materials.append({'id': f, 'name': f.replace('.inst.cfg', '').replace('generic_', 'Generic ').replace('_', ' ').title()})
            materials.sort(key=lambda x: x['name'])
            return materials
        except:
            return []
