from datetime import datetime
from pathlib import Path

import typer

import libecalc.common.time_utils
import libecalc.version
from ecalc_cli.emission_intensity import EmissionIntensityCalculator
from ecalc_cli.errors import EcalcCLIError
from ecalc_cli.infrastructure.file_resource_service import FileResourceService
from ecalc_cli.io.output import (
    emission_intensity_to_csv,
    write_flow_diagram,
    write_json,
    write_ltp_export,
    write_output,
    write_stp_export,
)
from ecalc_cli.logger import logger
from ecalc_cli.types import DateFormat, Frequency
from ecalc_neqsim_wrapper import CacheConfig, NeqSimFluidService, NeqsimService
from libecalc.common.datetime.utils import DateTimeFormats
from libecalc.common.math.numbers import Numbers
from libecalc.common.run_info import RunInfo
from libecalc.infrastructure.file_utils import OutputFormat, get_result_output, to_json
from libecalc.presentation.json_result.mapper import get_asset_result
from libecalc.presentation.yaml.file_configuration_service import FileConfigurationService
from libecalc.presentation.yaml.model import YamlModel


def run(
    model_file: Path = typer.Argument(
        ...,
        help="The Model YAML-file specifying time series inputs,"
        " facility inputs and the relationship between energy consumers.",
    ),
    output_frequency: Frequency = typer.Option(
        libecalc.common.time_utils.Frequency.NONE.name,
        "--output-frequency",
        "-f",
        "--outputfrequency",
        help="Frequency of output. Options are DAY, MONTH, YEAR. If not specified, it will give"
        " time steps equal to the union of all input given with INFLUENCE_TIME_VECTOR set to True."
        " Down-sampling the result may lead to loss of data, and rates such as MW may not add up to cumulative values",
    ),
    csv: bool = typer.Option(
        True,
        "--csv/--no-csv",
        "-c",
        help="Toggle output of csv data.",
    ),
    json: bool = typer.Option(
        False,
        "--json",
        help="Toggle output of json output.",
    ),
    output_folder: Path = typer.Option(
        None,
        "--output-folder",
        "-o",
        "--outputfolder",
        help="Outputfolder. Defaults to output/ relative to the yml setup file",
        show_default=False,
    ),
    name_prefix: str = typer.Option(
        None,
        "--name-prefix",
        "-n",
        "--nameprefix",
        help="Name prefix for output data. Defaults to name of setup file.",
    ),
    ltp_export: bool = typer.Option(
        False,
        "--ltp-export",
        help="In addition to standard output, a specific Long Term Prognosis (LTP) file "
        "will be provided for simple export of LTP relevant data (Tabular Separated Values).",
    ),
    stp_export: bool = typer.Option(
        False,
        "--stp-export",
        help="In addition to standard output, a specific Short Term Prognosis (STP) file "
        "will be provided for simple export of STP relevant data (Tabular Separated Values).",
    ),
    flow_diagram: bool = typer.Option(
        False,
        "--flow-diagram",
        help="Output the input model formatted to be displayed in a custom flow diagram format in JSON",
    ),
    detailed_output: bool = typer.Option(
        False,
        "--detailed-output",
        "--detailedoutput",
        help="Output detailed output. When False you will get basic results such as energy usage, power, time vector.",
    ),
    date_format_option: DateFormat = typer.Option(
        DateFormat.ISO_8601.value,
        "--date-format-option",
        help='Date format option. 0: "YYYY-MM-DD HH:MM:SS" (Accepted variant of ISO8601), 1: "YYYYMMDD HH:MM:SS" (ISO8601), 2: "DD.MM.YYYY HH:MM:SS". Default 0 (ISO 8601)',
    ),
    reference_cache_size: int | None = typer.Option(
        None,
        "--reference-cache-size",
        help="Max entries in reference fluid cache (note: default covers most use cases). "
        "Increase for models with many compositions. Set to 0 to disable.",
    ),
    flash_cache_size: int | None = typer.Option(
        None,
        "--flash-cache-size",
        help="Max entries in flash results cache (note: default covers most use cases). "
        "Increase for large models with many flash calculations. Set to 0 to disable.",
    ),
    use_experimental_neqsim: bool = typer.Option(
        False,
        "--use-experimental-neqsim",
        help="An improved implementation of Neqsim is available, but still experimental. After a short testing period "
        "this will be made default and not possible to change.",
    ),
):
    """CLI command to run a ecalc model."""
    if output_folder is None:
        output_folder = model_file.parent / "output"

    if name_prefix is None:
        name_prefix = model_file.stem

    frequency = libecalc.common.time_utils.Frequency[output_frequency.name]

    run_info = RunInfo(version=libecalc.version.current_version(), start=datetime.now())
    logger.info(f"eCalc™ simulation starting. Running {run_info}")
    validate_arguments(model_file=model_file, output_folder=output_folder)

    # Configure cache sizes if specified
    if reference_cache_size is not None or flash_cache_size is not None:
        defaults = CacheConfig.default()
        config = CacheConfig(
            reference_fluid_max_size=reference_cache_size or defaults.reference_fluid_max_size,
            flash_max_size=flash_cache_size or defaults.flash_max_size,
        )
        NeqSimFluidService.configure(config)

    with NeqsimService.factory(use_jpype=use_experimental_neqsim).initialize():
        configuration_service = FileConfigurationService(configuration_path=model_file)
        configuration = configuration_service.get_configuration()
        resource_service = FileResourceService(working_directory=model_file.parent, configuration=configuration)
        model = YamlModel(
            configuration=configuration,
            resource_service=resource_service,
        ).validate_for_run()

        if (flow_diagram or ltp_export) and (model.start is None or model.end is None):
            logger.warning(
                "When using Flow Diagram or Long Term Prognosis export, START and END should be specified in YAML to make sure you get the intended period as output. See documentation for more information."
            )

        if flow_diagram:
            write_flow_diagram(
                energy_model=model.get_energy_model(),
                model_period=model.variables.period,
                output_folder=output_folder,
                name_prefix=name_prefix,
            )

        model.evaluate_energy_usage()

        run_info.end = datetime.now()

        output_prefix: Path = output_folder / name_prefix

        results_dto = get_asset_result(model)

        if (
            frequency != libecalc.common.time_utils.Frequency.NONE
        ):  # Not sure why this had to be changed from Frequency.NONE to libecalc.common.time_utils.Frequency.NONE
            # Note: LTP can't use this resampled-result yet, because of differences in methodology.
            results_resampled = results_dto.resample(frequency)
        else:
            results_resampled = results_dto.model_copy()

        # Calculate emission intensity
        # Extract hydrocarbon_export_rate and emissions from results_dto.component_result
        hydrocarbon_export_rate = results_resampled.component_result.hydrocarbon_export_rate
        emissions = results_resampled.component_result.emissions

        emission_intensity_calculator = EmissionIntensityCalculator(
            hydrocarbon_export_rate=hydrocarbon_export_rate,
            emissions=emissions,
            frequency=frequency,
        )
        emission_intensity_results_resampled = emission_intensity_calculator.get_results()

        # rounding of results
        results_resampled = Numbers.format_results_to_precision(
            result=results_resampled,
            precision=6,
        )
        emission_intensity_results_resampled = Numbers.format_results_to_precision(
            result=emission_intensity_results_resampled,
            precision=6,
        )

        if csv:
            csv_data = get_result_output(
                results=results_resampled,
                output_format=OutputFormat.CSV,
                simple_output=not detailed_output,
                date_format_option=int(date_format_option.value),
            )
            write_output(output=csv_data, output_file=output_prefix.with_suffix(".csv"))

            # Emission intensity CSV
            intensity_csv_data = emission_intensity_to_csv(
                emission_intensity_results_resampled,
                DateTimeFormats.get_format(int(date_format_option.value)),
            )
            write_output(
                output=intensity_csv_data, output_file=output_prefix.with_name(f"{output_prefix.stem}_intensity.csv")
            )

        if json:
            write_json(
                results=results_resampled,
                output_folder=output_folder,
                name_prefix=name_prefix,
                run_info=run_info,
                date_format_option=int(date_format_option.value),
                simple_output=not detailed_output,
            )
            # Emission intensity JSON
            intensity_json_path = output_prefix.with_name(f"{output_prefix.stem}_intensity.json")
            json_intensity_data = to_json(
                emission_intensity_results_resampled,
                date_format_option=int(date_format_option.value),
            )
            with open(intensity_json_path, "w") as f:
                f.write(json_intensity_data)

        if ltp_export:
            write_ltp_export(
                model=model,
                output_folder=output_folder,
                frequency=frequency,  # Keep until alternative export option is in place (e.g. stp-export)
                name_prefix=name_prefix,
            )

        if stp_export:
            write_stp_export(
                model=model,
                output_folder=output_folder,
                frequency=frequency,  # Keep until alternative export option is in place (e.g. stp-export)
                name_prefix=name_prefix,
            )

        logger.info(f"eCalc™ simulation successful. Duration: {run_info.end - run_info.start}")


def validate_arguments(model_file: Path, output_folder: Path):
    """Helper function used to validate the CLI run command arguments.

    Args:
        model_file:
        output_folder:

    Returns:

    Raises:
        EcalcCLIError: If one of the arguments are invalid

    """
    if not model_file.is_file():
        raise EcalcCLIError(f"Setup file: {model_file.absolute()}: no such file")

    if not output_folder.parent.is_dir():
        raise EcalcCLIError(
            f"Output path {output_folder} not valid. Please specify an existing path or the name of a new folder in an existing path"
        )

    if not output_folder.is_dir():
        output_folder.mkdir()
