import argparse
import shutil
import re
from importlib.resources import files
from pathlib import Path
from typing import Optional, Sequence

from metacarp.app.command import Command
from metacarp.app.utils import snakemakeutils
from metacarp.app.utils.loggingutils import initialize_logging, logger
from metacarp.app.utils.snakemakeutils import parse_illumina_pairs
from metacarp.version import __version__


class METACARP:
    """
    Main class to run the MetaCARP pipeline.
    """

    def __init__(self, args: Optional[Sequence[str]] = None) -> None:
        """
        Initializes the main class.
        :param args: Arguments (optional)
        :return: None
        """
        self._args = METACARP._parse_arguments(args)
        self._path_html_out = self._args.output.absolute() / self._args.output_html
        if self._path_html_out.exists():
            self._path_html_out.unlink()

    def run(self) -> None:
        """
        Runs the main script.
        :return: None
        """
        logger.info('Starting MetaCARP workflow')
        self._check_dependencies()

        # Run Snakemake workflow
        path_snakefile = Path(str(files('metacarp').joinpath('resources/snakefiles/main_workflow.smk')))
        path_config = self.__create_config_file()

        snakemakeutils.run_snakemake(
            path_snakefile, path_config, [self._path_html_out], self._args.dir_working, self._args.threads)
        logger.info(f'Workflow finished successfully, output available in: {self._args.output}')

        # Copy the log file if it exists
        if (self._args.dir_working / 'metacarp.log').exists():
            shutil.copyfile(self._args.dir_working / 'metacarp.log', self._args.output / 'metacarp.log')

    @staticmethod
    def _parse_arguments(args: Optional[Sequence[str]]) -> argparse.Namespace:
        """
        Parses the command line arguments.
        :param args: Arguments
        :return: Parsed arguments.
        """
        parser = argparse.ArgumentParser()

        # Input and output
        parser.add_argument('--ilmn-in', help='Directory with Illumina input FASTQ files')
        parser.add_argument('--ont-in', help='Directory with ONT input FASTQ files')

        parser.add_argument(
            '--dir-working', type=Path, help='Working directory', default=Path.cwd())
        parser.add_argument('--output', required=True, type=Path, help='Output directory')
        parser.add_argument('--output-html', type=Path, help='Output report name', default='summary_screening.html')

        # Parameters
        parser.add_argument('--kraken-db', required=True, help='Directory with Kraken DB')
        parser.add_argument('--usual-suspects', type=Path, help='TSV file with species, taxid and group (Bacteria, Fungi, Plants, or Animals) of usual suspects',
                            default=str(files('metacarp').joinpath('resources/usual_suspects.tsv')))
        parser.add_argument('--allergens', type=Path, help='TSV file with taxids of allergens',
                            default=str(files('metacarp').joinpath('resources/allergens.tsv')))
        parser.add_argument('--cutoff-allergens', type=float, default=1, help='Minimal relative abundance (in %%) of allergens detected with Kraken2 to report')
        parser.add_argument('--cutoff-unclassified', type=float, default=5, help='Warning threshold for relative abundance (in %%) of unclassified reads detected with Kraken2')
        parser.add_argument('--cutoff-prok-fungi', type=float, default=0.1,
                            help='Minimal relative abundance (in %%) of prokaryotic or fungal species detected with Kraken2 to select for read mapping')
        parser.add_argument('--cutoff-euk', type=float, default=1,
                            help='Minimal relative abundance (in %%) of eukaryotic (non-fungal) species detected with Kraken2 to select for read mapping')
        parser.add_argument('--cutoff-confidence', default=Path(str(files('metacarp').joinpath('resources/confidence_cutoffs.json'))),
                            help='JSON configuration file with cutoffs for high and low confidence detection')

        # Threads
        parser.add_argument('--threads', type=int, default=4)
        parser.add_argument(
            '--version', help='Print version and exit', action='version', version=f'MetaCARP {__version__}')

        return parser.parse_args(args)



    def __create_config_file(self) -> Path:
        """
        Creates the config file for the Snakemake workflow.
        :return: Path to config file
        """
        input_dict = {}
        if self._args.ilmn_in is not None:
            input_dict.update(parse_illumina_pairs(Path(self._args.ilmn_in)))
        if self._args.ont_in is not None:
            for fq_file in sorted(Path(self._args.ont_in).glob('*.fastq.gz')):
                input_dict[fq_file.name.split('.')[0]] = {'fq': str(fq_file.absolute()), 'tech': 'ont'}
        if len(input_dict) == 0:
            raise FileNotFoundError(
                f'No input datasets found (searched in Illumina={self._args.ilmn_in}, ONT={self._args.ont_in})')
        config_data = {
            'kraken_db': self._args.kraken_db,
            'usual_suspects': str(self._args.usual_suspects),
            'allergens': str(self._args.allergens),
            'filters': {
                'cutoff_allergens': self._args.cutoff_allergens,
                'cutoff_unclassified': self._args.cutoff_unclassified,
                'cutoff_prok_fungi': self._args.cutoff_prok_fungi,
                'cutoff_euk': self._args.cutoff_euk,
                'cutoff_confidence': str(self._args.cutoff_confidence)
            },
            'input': input_dict,
            'output': {
                'html': str(self._path_html_out),
                'dir': str(self._args.output.absolute()),
                'dir_working': str(self._args.dir_working.absolute())
            },
        }
        return Path(snakemakeutils.generate_config_file(config_data, self._args.dir_working.absolute()))

    def _check_dependencies(self) -> None:
        """
        Checks if the required dependencies are available.
        :return: None
        """
        commands = {
            'kraken2': 'kraken2 --version',
            'minimap2': 'minimap2 --version',
            'ncbi-datasets': 'datasets --version',
            'fastp': 'fastp --version',
            'samtools': 'samtools --version',
        }
        logger.info(f'Checking dependencies')
        for tool, command in commands.items():
            command = Command(command)
            command.run(Path.cwd(), disable_logging=True)
            if not command.exit_code == 0:
                raise RuntimeError(f"Dependency '{tool}' not available")
            logger.info(f"{tool}: OK")