"""Command line api definition.

Uses click https://click.palletsprojects.com/en/8.1.x/ to manage complex cmdline configurations.
"""
from datetime import datetime
from pathlib import Path
from typing import List, Optional

import click

import perun
from perun.configuration import config, read_custom_config, save_to_config_callback


@click.group()
@click.version_option(version=perun.__version__)
@click.option(
    "-c",
    "--configuration",
    default="./.perun.ini",
    type=click.Path(exists=False, dir_okay=False, readable=True),
    is_eager=True,
    callback=read_custom_config,
    expose_value=False,
)
@click.option(
    "-f",
    "--format",
    type=click.Choice(["txt", "yaml", "yml", "json"]),
    help="report print format",
    callback=save_to_config_callback,
    expose_value=False,
)
@click.option(
    "-f",
    "--frequency",
    type=float,
    help="sampling frequency (in Hz)",
    callback=save_to_config_callback,
    expose_value=False,
)
@click.option(
    "--format",
    type=click.Choice(["txt", "yaml", "yml", "json"]),
    help="report print format",
    callback=save_to_config_callback,
    expose_value=False,
)
@click.option(
    "--data_out",
    type=click.Path(exists=False, dir_okay=True, file_okay=False),
    help="experiment data output directory",
    callback=save_to_config_callback,
    expose_value=False,
)
@click.option(
    "-l",
    "--log_lvl",
    type=click.Choice(["DEBUG", "INFO", "WARN", "ERROR", "CRITICAL"]),
    help="Loggging level",
    callback=save_to_config_callback,
    expose_value=False,
)
@click.option(
    "--pue",
    type=float,
    help="Data center Power usage efficiency",
    callback=save_to_config_callback,
    expose_value=False,
)
@click.option(
    "--location",
    type=str,
    help="Data center location",
    callback=save_to_config_callback,
    expose_value=False,
)
def cli():
    """Perun: Energy measuring and reporting tool."""


@cli.command()
@click.option(
    "--default",
    is_flag=True,
    show_default=True,
    default=False,
    help="Print default configuration",
)
def showconf(default: bool):
    """Print current perun configuration in INI format."""
    import sys

    from perun.configuration import _default_config

    if default:
        config.read_dict(_default_config)
        config.write(sys.stdout)
    else:
        config.write(sys.stdout)


@cli.command()
@click.argument("exp_hdf5", type=click.Path(exists=True))
def postprocess(exp_hdf5: str):
    """
    Apply post-processing to EXP_HDF5 experiment file.

    EXP_HDF5 is an hdf5 file generated by perun after monitoring a script, containing data gathered from hardware devices.
    """
    from mpi4py import MPI

    expPath = Path(exp_hdf5)
    expStrg = perun.ExperimentStorage(expPath, MPI.COMM_WORLD, write=True)
    perun.postprocessing(expStrg)
    expStrg.close()


@cli.command()
@click.argument("exp_hdf5", type=click.Path(exists=True))
def report(exp_hdf5: str):
    """Print consumption report from EXP_HDF5 on the command line on the desired format.

    EXP_HDF5 is an hdf5 file generated by perun after monitoring a script, containing data gathered from hardware devices.
    """
    from mpi4py import MPI

    expPath = Path(exp_hdf5)
    expStrg = perun.ExperimentStorage(expPath, MPI.COMM_WORLD)
    print(perun.report(expStrg, format=config.get("report", "format")))
    expStrg.close()


@cli.command(context_settings={"ignore_unknown_options": True})
@click.argument("script", type=click.Path(exists=True))
@click.argument("script_args", nargs=-1)
def monitor(
    script: str,
    script_args: tuple,
):
    """
    Gather power consumption from hardware devices while SCRIPT [SCRIPT_ARGS] is running.

    SCRIPT is a path to the python script to monitor, run with arguments SCRIPT_ARGS.
    """
    import sys
    from multiprocessing import Event, Process, Queue

    from mpi4py import MPI

    from perun import log
    from perun.backend import backends

    comm = MPI.COMM_WORLD
    start_event = Event()
    stop_event = Event()

    # Setup script arguments
    filePath: Path = Path(script)
    outPath: Path = Path(config.get("monitor", "data_out"))
    log.debug(f"Script path: {filePath}")
    argIndex = sys.argv.index(str(filePath))
    sys.argv = sys.argv[argIndex:]
    log.debug(f"Script args: { sys.argv }")

    # Get node devices
    log.debug(f"Backends: {backends}")
    lDeviceIds: List[str] = perun.getDeviceConfiguration(comm, backends)

    for backend in backends:
        backend.close()

    log.debug(f"Rank {comm.rank} - lDeviceIds : {lDeviceIds}")

    # If assigned devices, start subprocess
    if len(lDeviceIds) > 0:
        queue: Queue = Queue()
        perunSP = Process(
            target=perun.perunSubprocess,
            args=[
                queue,
                start_event,
                stop_event,
                lDeviceIds,
                config.getfloat("monitor", "frequency"),
            ],
        )
        perunSP.start()
        start_event.wait()

    # Sync everyone
    comm.barrier()
    start = datetime.now()

    # Start script
    try:
        with open(str(filePath), "r") as scriptFile:
            exec(scriptFile.read(), {"__name__": "__main__"})
    except Exception as e:
        log.error("Failed to open file ", filePath)
        log.error(e)
        stop_event.set()
        return

    stop_event.set()
    stop = datetime.now()

    lStrg: Optional[perun.LocalStorage]
    # Obtain perun subprocess results
    if len(lDeviceIds) > 0:
        perunSP.join()
        perunSP.close()
        lStrg = queue.get()
    else:
        lStrg = None

    # Sync
    comm.barrier()

    # Save raw data to hdf5

    if comm.rank == 0:
        if not outPath.exists():
            outPath.mkdir(parents=True)

    scriptName = filePath.name.replace(filePath.suffix, "")
    resultPath = outPath / f"{scriptName}.hdf5"
    log.debug(f"Result path: {resultPath}")
    expStrg = perun.ExperimentStorage(resultPath, comm, write=True)
    expId = expStrg.addExperimentRun(lStrg)
    if lStrg and config.getboolean("horeka", "enabled"):
        from perun.extras.horeka import get_horeka_measurements

        get_horeka_measurements(
            comm, outPath, expStrg.experimentName, expId, start, stop
        )

    # Post post-process
    comm.barrier()
    perun.postprocessing(expStorage=expStrg)
    if comm.rank == 0:
        print(
            perun.report(expStrg, expIdx=expId, format=config.get("report", "format"))
        )
    comm.barrier()
    expStrg.close()


def main():
    """Cli entrypoint."""
    cli(auto_envvar_prefix="PERUN")
