import os
import subprocess
import configparser
import uuid
import shutil
from app.models import SystemConfig


class PrusaSlicerEngine:
    def __init__(self, print_config_folder=None, config_slice_bin_path=None):
        self.name = "prusa_slicer"
        self.display_name = "PrusaSlicer"
        self.config_slice_bin_path = config_slice_bin_path
        self.is_available = self._check_available()
        self.print_config_folder = os.path.join(print_config_folder, 'prusa_slicer') if print_config_folder else None

    def _check_available(self):
        try:
            # Prefer explicit environment variable, then PATH, then a bundled AppImage under the repo
            prusa_bin = self.config_slice_bin_path or shutil.which('prusa-slicer') or shutil.which('prusa-slicer.exe')
            if not prusa_bin:
                return False
            result = subprocess.run([prusa_bin, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            return b"Usage:" in result.stdout or b"Slic3r" in result.stdout or b"PrusaSlicer" in result.stdout or result.returncode == 0
        except Exception:
            return False

    def add_ini_keys(self, config_path, target_section, all_configs):
        config = configparser.ConfigParser(interpolation=None)
        config.read(config_path)
        if target_section not in config:
            config[target_section] = {}
        for k, v in config[target_section].items():
            all_configs[k] = v

    def slice(self, app, stl_filepath, gcode_filepath, **kwargs):
        """
        Slices via prusa-slicer CLI mapping standard kwargs to PRUSA parameters where possible.
        """
        try:
            prusa_bin = self.config_slice_bin_path or shutil.which('prusa-slicer') or shutil.which('prusa-slicer.exe')

            # Base command
            command = [prusa_bin, '-g', stl_filepath, '--output', gcode_filepath]
            
            # Map quality, infill, supports to PrusaSlicer CLI arguments.
            # Example defaults, normally these would load from an .ini or be dynamically matched.
            quality_preset = kwargs.get('quality_preset')
            material_preset = kwargs.get('material_preset')
            infill_density = kwargs.get('infill_density')
            support_enable = kwargs.get('support_enable')
            support_pattern = kwargs.get('support_pattern')

            # print(support_pattern)
            all_configs = {}
            db_printer = SystemConfig.query.filter_by(key='default_printer').first()
            p_val = db_printer.value if db_printer and db_printer.value else 'Ender3_V3_SE'
            if not p_val.endswith('.ini'): p_val += '.ini'
            printer_ini = os.path.join(self.print_config_folder, 'printers', p_val) if self.print_config_folder else None
            if printer_ini and os.path.exists(printer_ini):
                self.add_ini_keys(printer_ini, 'settings', all_configs)

            if quality_preset:
                q_ini = os.path.join(self.print_config_folder, 'quality', f"{quality_preset}.ini") if self.print_config_folder else None
                if q_ini and os.path.exists(q_ini):
                    self.add_ini_keys(q_ini, 'settings', all_configs)

            if material_preset:
                m_ini = os.path.join(self.print_config_folder, 'materials', f"{material_preset}.ini") if self.print_config_folder else None
                if m_ini and os.path.exists(m_ini):
                    self.add_ini_keys(m_ini, 'settings', all_configs)

            if infill_density is not None:
                command.extend([f"--fill-density={infill_density}%"])
                
            if support_enable and support_enable != 'false':
                # command.append("--support-material")
                if support_enable == 'buildplate':
                    command.append("--support-material-buildplate-only")
                # PrusaSlicer equivalent for tree supports => organic
                support_pattern_ini = os.path.join(self.print_config_folder, 'supports', f'{support_pattern}.ini') if self.print_config_folder else None
                if support_pattern_ini and os.path.exists(support_pattern_ini):
                    self.add_ini_keys(support_pattern_ini, 'settings', all_configs)
            else:
                # Load the default no_support.ini if no support is enabled
                no_support_ini = os.path.join(self.print_config_folder, 'supports', 'no_support.ini') if self.print_config_folder else None
                if no_support_ini and os.path.exists(no_support_ini):
                    self.add_ini_keys(no_support_ini, 'settings', all_configs)
                else:
                    all_configs['support_material'] = '0'
            
            tmp_ini_filename = f"tmp_{uuid.uuid4().hex}.ini"
            tmp_ini_path = os.path.join(app.config['UPLOAD_FOLDER'], tmp_ini_filename)
            with open(tmp_ini_path, 'w') as f:
                for key, value in all_configs.items():
                    f.write(f"{key} = {value}\n")
            
            command.extend(["--load", tmp_ini_path])

            env = os.environ.copy()
            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:
                # Clean up the temporary .ini file
                os.remove(tmp_ini_path)
                return True, None
            else:
                err_msg = stderr.decode() if stderr else "Unknown prusa-slicer error"
                app.logger.error(f"PrusaSlicer Error: {err_msg}")
                return False, err_msg
                
        except Exception as e:
            app.logger.error(f"PrusaSlicer Exception: {e}")
            return False, str(e)

    def get_quality_presets(self):
        all_files = [f for f in os.listdir(os.path.join(self.print_config_folder, "quality")) if f.endswith('.ini')] if self.print_config_folder else []
        quality_presets = []
        for file in all_files:
            with open(os.path.join(self.print_config_folder, "quality", file), 'r') as f:
                config = configparser.ConfigParser()
                config.read_file(f)
                if 'metadata' in config:
                    quality_presets.append({
                        'id': file.replace('.ini', ''),
                        'name': config['metadata'].get('show_name', file.replace('.ini', '').replace('_', ' '))
                    })
        return quality_presets

    def get_support_patterns(self):
        all_files = [f for f in os.listdir(os.path.join(self.print_config_folder,"supports")) if f.endswith('.ini')] if self.print_config_folder else []
        support_presets = []
        for file in all_files:
            with open(os.path.join(self.print_config_folder, "supports", file), 'r') as f:
                config = configparser.ConfigParser()
                config.read_file(f)
                if 'metadata' in config:
                    support_presets.append({
                        'id': file.replace('.ini', ''),
                        'name': config['metadata'].get('show_name', file.replace('.ini', '').replace('_', ' '))
                    })
        return support_presets

    def get_materials(self):
        all_files = [f for f in os.listdir(os.path.join(self.print_config_folder, "materials")) if f.endswith('.ini')] if self.print_config_folder else []
        materials = []
        for file in all_files:
            with open(os.path.join(self.print_config_folder, "materials", file), 'r') as f:
                config = configparser.ConfigParser()
                config.read_file(f)
                if 'metadata' in config:
                    materials.append({
                        'id': file.replace('.ini', ''),
                        'name': config['metadata'].get('show_name', file.replace('.ini', '').replace('_', ' '))
                    })
        return materials


    def get_bed_dimensions(self):
        try:
            db_printer = SystemConfig.query.filter_by(key='default_printer').first()
            p_val = db_printer.value if db_printer and db_printer.value else 'Ender3_V3_SE.ini'
            if not p_val.endswith('.ini'): p_val += '.ini'
            path = os.path.join(self.print_config_folder, 'printers', p_val)
            config = configparser.ConfigParser()
            config.read(path)
            if 'settings' in config and 'bed_shape' in config['settings']:
                # format is usually like 0x0,220x0,220x220,0x220
                coords = config['settings']['bed_shape'].split(',')
                max_x = max([float(c.split('x')[0]) for c in coords])
                max_y = max([float(c.split('x')[1]) for c in coords])
                # height
                h = 250
                if 'max_print_height' in config['settings']:
                    h = float(config['settings']['max_print_height'])
                return max_x, max_y, h
        except:
            pass
        return 220, 220, 250
    def get_all_printers(self):
        all_files = [f for f in os.listdir(os.path.join(self.print_config_folder, "printers")) if f.endswith('.ini')] if self.print_config_folder else []
        printers = []
        for file in all_files:
            with open(os.path.join(self.print_config_folder, "printers", file), 'r') as f:
                config = configparser.ConfigParser()
                config.read_file(f)
                if 'metadata' in config:
                    printers.append({
                        'id': file.replace('.ini', ''),
                        'name': config['metadata'].get('show_name', file.replace('.ini', '').replace('_', ' '))
                    })
        return printers
