import argparse
import sys
from pathlib import Path
from typing import List, Tuple
from unittest.mock import patch

from . import _version
__version__ = _version.get_versions()['version']

parser = argparse.ArgumentParser(prog='main', description='Convert a high-level yaml workflow file to CWL.')

parser.add_argument('--yaml', type=str,
                    required=(not ('--generate_config' in sys.argv or '--generate_schemas' in sys.argv)),
                    help='Yaml workflow file')
group_gen = parser.add_mutually_exclusive_group()
group_gen.add_argument('--generate_schemas', default=False, action="store_true",
                       help='Generate schemas for the files in config.json (search_paths_wic and search_paths_cwl)')
group_gen.add_argument('--generate_config', default=False, action="store_true",
                       help='''Generate default config in wic/global_config.json
                       with default search_paths_wic and search_paths_cwl''')
parser.add_argument('--inputs_file', type=str, required=False, default='',
                    help='Additional inputs Yaml file')
parser.add_argument('--config_file', type=str, required=False, default=str(Path().home()/'wic'/'global_config.json'),
                    help='User provided (JSON) config file')
# version action exits the parser
# Ref : https://github.com/python/cpython/blob/1f515e8a109204f7399d85b7fd806135166422d9/Lib/argparse.py#L1167
parser.add_argument('--version', action='version', version=__version__,
                    default='==SUPPRESS==', help='Current version of the Workflow Inference Compiler')
parser.add_argument('--homedir', type=str, required=False, default=str(Path().home()),
                    help='''The users home directory.
                    This is necessary because CWL clears environment variables (e.g. HOME)''')
parser.add_argument('--pull_dir', type=str, required=False, default=str(Path().cwd()),
                    help='''The user specified pull directory for (singularity) image pull.
                    The default is the current working directory i.e. `pwd`''')
parser.add_argument('--insert_steps_automatically', default=False, action="store_true",
                    help='''Attempt to fix inference failures by speculatively
                    inserting workflow steps from a curated whitelist.''')
parser.add_argument('--no_provenance', default=False, action="store_true",
                    help='''Do not use the --provenance feature of CWL.
                    (You should only disable provenance if absolutely necessary.)''')
parser.add_argument('--copy_output_files', default=False, action="store_true",
                    help='Copies output files from the cachedir to outdir/ (automatically enabled with --run_local)')
parser.add_argument('--cwl_inline_runtag', default=False, action="store_true",
                    help='Copies cwl adapter file contents inline into the final .cwl in autogenerated/')
# This is a hidden flag
parser.add_argument('--ignore_dir_path', type=bool,
                    required=False, default=False, help=argparse.SUPPRESS)

parser.add_argument('--parallel', default=False, action="store_true",
                    help='''When running locally, execute independent steps in parallel.
                    \nThis is required for real-time analysis, but it may cause issues with
                    \nhanging (particularly when scattering). See user guide for details.''')
parser.add_argument('--quiet', default=False, action="store_true",
                    help='''Disable verbose output. This will not print out the commands used for each step,
                    and it will capture all stdout/stderr into log files for each step.''')
parser.add_argument('--cwl_runner', type=str, required=False, default='cwltool', choices=['cwltool', 'toil-cwl-runner'],
                    help='The CWL runner to use for running workflows locally.')
parser.add_argument('--allow_raw_cwl', default=False, action="store_true",
                    help='Do not check whether the input to a workflow step refers to the workflow inputs: tag')
parser.add_argument('--ignore_docker_install', default=False, action="store_true",
                    help='''Do not check whether docker is installed before running workflows.
                    \n--ignore_docker_install does NOT change whether or not any step in your workflow uses docker!''')
parser.add_argument('--ignore_docker_processes', default=False, action="store_true",
                    help='Do not check whether there are too many running docker processes before running workflows.')
parser.add_argument('--container_engine', default='docker',
                    help='Specify which command to use to run OCI containers.')

parser.add_argument('--docker_remove_entrypoints', default=False, action="store_true",
                    help='''Remove entrypoints from docker images before running workflows.
                    See https://cwl.discourse.group/t/override-docker-entrypoint-in-command-line-tool/695/2
                    If your CWL CommandLineTools rely on non-portable docker entrypoints
                    (i.e. if they do NOT explicitly have baseCommand: and/or arguments: tags)
                    then this flag will almost certainly cause your workflows to fail.
                    In other words, this flag can be used to tell you if your workflows are
                    trapped inside a walled garden of docker containers or if there is at least a chance that
                    they can be run in dev environments (conda, etc) and/or directly on the host machine.''')

parser.add_argument('--partial_failure_enable', default=False, action="store_true",
                    help='''Let workflows to continue with partial failures
                    \n(i.e. run next steps after failure step by guarding for possible no input)''')

parser.add_argument('--partial_failure_success_codes', nargs='*', type=int, default=[0, 1], required=False,
                    help='Let users add custom error codes to be treated as success')

parser.add_argument('--partial_failure_success_codes_range', nargs=2, type=int, default=[0, 1], required=False,
                    help='Let users add custom error code range to be treated as success')

group_run = parser.add_mutually_exclusive_group()
group_run.add_argument('--generate_run_script', default=False, action="store_true",
                       help='Just generates run.sh and exits. Does not actually invoke ./run.sh')
group_run.add_argument('--run_local', default=False, action="store_true",
                       help='After generating the cwl file(s), run it on your local machine.')
group_run.add_argument('--generate_cwl_workflow', required=False, default=False, action="store_true",
                       help='Compile the workflow without pulling the docker image')
parser.add_argument('--cwl_inline_subworkflows', default=False, action="store_true",
                    help='Before generating the cwl file, inline all subworkflows.')
parser.add_argument('--inference_disable', default=False, action="store_true",
                    help='Disables use of the inference algorithm when compiling.')
parser.add_argument('--inference_use_naming_conventions', default=False, action="store_true",
                    help='Enables the use of naming conventions in the inference algorithm')
parser.add_argument('--validate_plugins', default=False, action="store_true",
                    help='Validate all CWL CommandLineTools')
parser.add_argument('--ignore_validation_errors', default=False, action="store_true",
                    help='Temporarily ignore validation errors. Do not use this permanently!')
parser.add_argument('--no_skip_dollar_schemas', default=False, action="store_true",
                    help='''Does not skip processing $schemas tags in CWL files for performance.
                    Skipping significantly improves initial validation performance, but is not always desired.
                    See https://github.com/common-workflow-language/cwltool/issues/623''')
parser.add_argument('--cachedir', type=str, required=False, default='cachedir',
                    help='The directory to save intermediate results; useful with RealtimePlots.py')

parser.add_argument('--graphviz', default=False, action="store_true",
                    help='Generate a DAG using graphviz.')
parser.add_argument('--graph_label_edges', default=False, action="store_true",
                    help='Label the graph edges with the name of the intermediate input/output.')
parser.add_argument('--graph_label_stepname', default=False, action="store_true",
                    help='Prepend the step name to each step node.')
parser.add_argument('--graph_show_inputs', default=False, action="store_true",
                    help='Add nodes to the graph representing the workflow inputs.')
parser.add_argument('--graph_show_outputs', default=False, action="store_true",
                    help='Add nodes to the graph representing the workflow outputs.')
parser.add_argument('--graph_inline_depth', type=int, required=False, default=sys.maxsize,
                    help='Controls the depth of subgraphs which are displayed.')
parser.add_argument('--graph_dark_theme', default=False, action="store_true",
                    help='Changees the color of the fonts and edges from white to black.')
parser.add_argument('--custom_net', type=str, required=False,
                    help='Passes --custom-net flag to cwltool.')
parser.add_argument('--toil_passthrough_flags', type=str, default='no', required=False,
                    help='''Indicates that the user is passing flags to the Toil backend.
                    No checks are done on the flags or values passed. User must verify that they are sending correct flags.
                    only two valid values of this flag 'yes' or 'no'.
                    If set to 'no' (default) passthrough flags won't be sent to the Toil backend.''')


def get_args(yaml_path: str = '', suppliedargs: list[str] = []) -> argparse.Namespace:
    """This is used to get mock command line arguments, default + suppled args

    Returns:
        argparse.Namespace: The mocked command line arguments
    """
    defaultargs = ['sophios', '--yaml', yaml_path]  # ignore --yaml
    testargs = defaultargs + suppliedargs
    with patch.object(sys, 'argv', testargs):
        args = parser.parse_args()
    return args


def get_known_and_unknown_args(yaml_path: str = '', suppliedargs: list[str] = []) -> Tuple[argparse.Namespace, List[str]]:
    """This is used to get mock command line arguments, default + suppled args

    Returns:
        argparse.Namespace: The mocked command line arguments
    """
    defaultargs = ['sophios', '--yaml', yaml_path]  # ignore --yaml
    testargs = defaultargs + suppliedargs
    with patch.object(sys, 'argv', testargs):
        known_args, unknown_args = parser.parse_known_args()
    return known_args, unknown_args
