import os, subprocess, shutil, time
from abc import ABC, abstractmethod
from workflow.log import logcolor
from pathlib import Path

class WorkflowRunnerBase(ABC):
    def __init__(
        self,
        logger,                                 # REQUIRED, no default
        manifest_list,                          # REQUIRED, no default
        input_path,                             # REQUIRED, no default
        output_path,                            # REQUIRED, no default
        additional_args,                        # REQUIRED, no default
        threads=2,                              # default = 2
        stepname="stepname_DEFAULT",            # default = stepname_DEFAULT
        software_name="software_name_DEFAULT",  # default = software_name_DEFAULT
        unneeded_args=None,                     # default = None
        output_type="output_DEFAULT",           # default = output_DEFAULT
        leaf=False,                             # default = False 		(optional)
        output_for_next_step=True,              # default = True 		(optional)
        shell=False,                            # default = False 		(optional)
        carry_previous=False,                   # default = False 		(optional)
        rescan_output=False,                    # default = False 		(optional)
        mode=None				# default = None 		(optional)
    ):
        self.logger = logger
        self.manifest_list = manifest_list
        self.software_name = software_name
        self.unneeded_args = unneeded_args or []
        self.threads = threads
        self.stepname = stepname
        self.input_path = os.path.abspath(input_path)
        self.output_path = os.path.abspath(output_path)
        self.additional_args = additional_args
        self.output_type = output_type
        self.leaf = leaf
        self.output_for_next_step = output_for_next_step
        self.shell = shell
        self.carry_previous = carry_previous
        self.rescan_output = rescan_output
        self.mode = mode
        self.start_time = None
        self.end_time = None

        self.input_file_list = []
        self.command_queue = []

        self.dynamic_flags_from_args()
        self.dynamic_mode()

    def dynamic_mode(self):
        pass

    def dynamic_flags_from_args(self):
        # Optional hook for subclasses to adjust attributes based on CLI args (e.g., 'leaf' mode).
        pass

    def rediscover_output(self):
        discover_list = [f for f in os.listdir(self.output_path) if os.path.isfile(os.path.join(self.output_path, f))]
        new_output = []
        for file in discover_list:
            new_output.append({
                "id": Path( os.path.basename(file) ).stem,
                "input1": None,
                "input2": None,
                "output1": f"{os.path.join( self.output_path, Path( os.path.basename(file) ).stem )}.{self.output_type}",
                "output2": None,
            })
        return new_output

    def create_output_dir(self):
        # Create directories for the output of this particular step
        os.makedirs(self.output_path, exist_ok=True)

    def dry_run(self):
        self.input_file_list = [{
            "id": "SampleID",
            "horizon": "Horizon1",
            "input1": f"{self.input_path}/input_file1",
            "input2": f"{self.input_path}/input_file2",
            "output1": f"{self.output_path}/output_file1",
            "output2": f"{self.output_path}/output_file2"
        }]
        # Log the whole process of this step without running anything
        self.logger.info(f"-> {logcolor.INFO}{logcolor.UNDERLINE}{logcolor.BOLD}{self.stepname}{logcolor.ENDC}")
        self.logger.info(f"--> {logcolor.INFO}Create directory for output {self.output_path}{logcolor.ENDC}")
        self.build_command_queue()
        sample_command = self.command_queue[0].replace(" -", "\n -")
        self.logger.info(f"--> {logcolor.INFO}{self.software_name} command to run for each input:{logcolor.ENDC}\n{sample_command}\n")

    def carry(self):
        self.logger.info(f"{logcolor.INFO}--> Copying previous step output...{logcolor.ENDC}")
        for root, _, files in os.walk(self.input_path):
            for file in files:
                src_path = os.path.join(root, file)
                rel_path = os.path.relpath(src_path, self.input_path)
                dst_path = os.path.join(self.output_path, rel_path)
                os.makedirs(os.path.dirname(dst_path), exist_ok=True)
                shutil.copy2(src_path, dst_path)
        self.logger.info(f"{logcolor.INFO}--> Done.{logcolor.ENDC}")



    def run(self):
        self.start_time = time.time()

        self.logger.info(f"-> {logcolor.INFO}{logcolor.UNDERLINE}{logcolor.BOLD}{self.stepname}{logcolor.ENDC}")
        if self.carry_previous:
            self.carry()

        # Loop through command queue until complete
        for i, cmd in enumerate(self.command_queue, 1):
            self.logger.info(f"{logcolor.INFO}--> Starting Subprocess {i}/{len(self.command_queue)}...{logcolor.ENDC}")
            self.logger.info(f"{logcolor.INFO}--> Command: {logcolor.REFERENCE}{cmd}{logcolor.ENDC}")

            # Create a new subprocess
            process = subprocess.Popen(
                cmd if self.shell else cmd.split(),
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                shell=self.shell
            )

            # Log all output from the subprocess
            stdout, stderr = process.communicate()
            return_code = process.returncode
            self.logger.info(stdout.decode())
            self.logger.error(stderr.decode())

            # Clean up after subprocess completes
            if return_code == 0:
                self.logger.info(f"{logcolor.INFO}--> Subprocess {i}/{len(self.command_queue)} complete, return code {logcolor.REFERENCE}{return_code}{logcolor.ENDC}")
            else:
                self.logger.info(f"{logcolor.ERROR}--> Subprocess {i}/{len(self.command_queue)} error, return code {logcolor.REFERENCE}{return_code}{logcolor.ENDC}")
                self.logger.info(f"{logcolor.ERROR}--> Halting workflow due to non-zero subprocess return code.{logcolor.ENDC}")
                exit(1)
        self.end_time = time.time()

        # Account for non-standard outputs
        if self.rescan_output:
            return self.rediscover_output()
        else:
            return None

    @abstractmethod
    def find_input_files(self):
        pass

    @abstractmethod
    def build_command_queue(self):
        pass
