from pathlib import Path
import argparse
import copy
import yaml


import uvicorn
from fastapi import FastAPI, Request, status
from fastapi.middleware.cors import CORSMiddleware

from sophios import __version__, compiler
from sophios import run_local, input_output
from sophios.utils_graphs import get_graph_reps
from sophios.utils_yaml import wic_loader
from sophios import utils_cwl
from sophios.cli import get_args
from sophios.wic_types import CompilerInfo, Json, Tool, Tools, StepId, YamlTree, Cwl, NodeData
from sophios.api.utils import converter
# from .auth.auth import authenticate


# helper functions


def remove_dot_dollar(tree: Cwl) -> Cwl:
    """Removes . and $ from dictionary keys, e.g. $namespaces and $schemas. Otherwise, you will get
    {'error': {'statusCode': 500, 'message': 'Internal Server Error'}}
    This is due to MongoDB:
    See https://www.mongodb.com/docs/manual/reference/limits/#Restrictions-on-Field-Names
    Args:
        tree (Cwl): A Cwl document
    Returns:
        Cwl: A Cwl document with . and $ removed from $namespaces and $schemas
    """
    tree_str = str(yaml.dump(tree, sort_keys=False, line_break='\n', indent=2))
    tree_str_no_dd = tree_str.replace('$namespaces', 'namespaces').replace(
        '$schemas', 'schemas').replace('.wic', '_wic')
    tree_no_dd: Cwl = yaml.load(tree_str_no_dd, Loader=wic_loader())  # This effectively copies tree
    return tree_no_dd


def get_yaml_tree(req: Json) -> Json:
    """
    Get the Sophios yaml tree from incoming JSON
    Args:
        req (JSON): A raw JSON content of incoming JSON object
    Returns:
        Cwl: A Cwl document with . and $ removed from $namespaces and $schemas
    """
    wkflw_name = "generic_workflow"
    # args = converter.get_args(wkflw_name)
    # yaml_tree_json: Json = converter.wfb_to_wic(req)
    yaml_tree_json: Json = {}
    return yaml_tree_json


def run_workflow(compiler_info: CompilerInfo, args: argparse.Namespace) -> int:
    """
    Get the Sophios yaml tree from incoming JSON
    Args:
        req (JSON): A raw JSON content of incoming JSON object
    Returns:
        Cwl: A Cwl document with . and $ removed from $namespaces and $schemas
    """
    # ========= WRITE OUT =======================
    input_output.write_to_disk(compiler_info.rose, Path('autogenerated/'), relative_run_path=True)
    # ======== TEST RUN =========================
    retval = run_local.run_local(args, compiler_info.rose, args.cachedir, 'cwltool', False)
    return retval


app = FastAPI()

origins = ["*"]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


@app.get("/", status_code=status.HTTP_200_OK)
# @authenticate
async def root(request: Request) -> Json:
    """The api has 1 route: compile

    Returns:
        Dict[str, str]: {"message": "The api has 1 route: compile"}
    """
    return {"message": "The api has 1 route: compile"}


@app.post("/compile")
# @authenticate
async def compile_wf(request: Request) -> Json:
    """The compile route compiles the json object from http request object built elsewhere

    Args:
        request (Request): request object built elsewhere

    Returns:
        compute_workflow (JSON): workflow json object ready to submit to compute
    """
    print('---------- Compile Workflow! ---------')
    # ========= PROCESS REQUEST OBJECT ==========
    req: Json = await request.json()
    # clean up and convert the incoming object
    req = converter.raw_wfb_to_lean_wfb(req)
    wkflw_name = "generic_workflow"
    args = get_args(wkflw_name)

    workflow_temp = {}
    if req["links"] != []:
        for node in req["nodes"]:
            workflow_temp["id"] = node["id"]
            workflow_temp["step"] = node["cwlScript"]  # Assume dict form
    else:  # A single node workflow
        node = req["nodes"][0]
        workflow_temp = node["cwlScript"]

    workflow_can = utils_cwl.desugar_into_canonical_normal_form(workflow_temp)

    # ========= BUILD WIC COMPILE INPUT =========
    tools_cwl: Tools = {StepId(content["id"], "global"):
                        Tool(".", content["run"]) for content in workflow_can["steps"]}
    # run tag will have the actual CommandLineTool
    wic_obj = {'wic': workflow_can.get('wic', {})}
    plugin_ns = wic_obj['wic'].get('namespace', 'global')

    graph = get_graph_reps(wkflw_name)
    yaml_tree: YamlTree = YamlTree(StepId(wkflw_name, plugin_ns), workflow_can)

    # ========= COMPILE WORKFLOW ================
    compiler_info: CompilerInfo = compiler.compile_workflow(yaml_tree, args, [], [graph], {}, {}, {}, {},
                                                            tools_cwl, True, relative_run_path=True, testing=False)

    # =========== OPTIONAL RUN ==============
    print('---------- Run Workflow locally! ---------')
    retval = run_workflow(compiler_info, args)

    # ======== OUTPUT PROCESSING ================
    # ========= PROCESS COMPILED OBJECT =========
    sub_node_data: NodeData = compiler_info.rose.data
    yaml_stem = sub_node_data.name
    cwl_tree = sub_node_data.compiled_cwl
    yaml_inputs = sub_node_data.workflow_inputs_file
    cwl_tree_no_dd = remove_dot_dollar(cwl_tree)
    yaml_inputs_no_dd = remove_dot_dollar(yaml_inputs)

    # Convert the compiled yaml file to json for labshare Compute.
    cwl_tree_run = copy.deepcopy(cwl_tree_no_dd)
    # for step_key in cwl_tree['steps']:
    #     step_name_i = step_key
    #     step_name_i = step_name_i.replace('.yml', '_yml')  # Due to calling remove_dot_dollar above
    #     # step_name = '__'.join(step_key.split('__')[3:])  # Remove prefix
    #     # Get step CWL from templates
    #     # run_val = next((tval['cwlScript']
    #     #                for _, tval in ict_plugins.items() if step_name == tval['name']), None)
    #     # cwl_tree_run['steps'][step_name_i]['run'] = run_val

    compute_workflow: Json = {}
    compute_workflow = {
        "name": yaml_stem,
        "cwlJobInputs": yaml_inputs_no_dd,
        **cwl_tree_run
    }
    compute_workflow["retval"] = str(retval)
    return compute_workflow


if __name__ == '__main__':
    uvicorn.run(app, host="0.0.0.0", port=3000)


# # ========= PROCESS COMPILED OBJECT =========
    # sub_node_data: NodeData = compiler_info.rose.data
    # yaml_stem = sub_node_data.name
    # cwl_tree = sub_node_data.compiled_cwl
    # yaml_inputs = sub_node_data.workflow_inputs_file

    # # ======== OUTPUT PROCESSING ================
    # cwl_tree_no_dd = remove_dot_dollar(cwl_tree)
    # yaml_inputs_no_dd = remove_dot_dollar(yaml_inputs)

    # # Convert the compiled yaml file to json for labshare Compute.
    # cwl_tree_run = copy.deepcopy(cwl_tree_no_dd)
    # for step_key in cwl_tree['steps']:
    #     step_name_i = step_key
    #     step_name_i = step_name_i.replace('.yml', '_yml')  # Due to calling remove_dot_dollar above
    #     step_name = '__'.join(step_key.split('__')[3:])  # Remove prefix

    #     # Get step CWL from templates
    #     run_val = next((tval['cwlScript']
    #                    for _, tval in ict_plugins.items() if step_name == tval['name']), None)
    #     cwl_tree_run['steps'][step_name_i]['run'] = run_val

    # TODO: set name and driver in workflow builder ui
    # compute_workflow: Json = {}
    # compute_workflow = {
    #     "name": yaml_stem,
    #     "driver": "argo",
    #     # "driver": "cwltool",
    #     "cwlJobInputs": yaml_inputs_no_dd,
    #     **cwl_tree_run
    # }
