from __future__ import annotations

import typing

import flood

if typing.TYPE_CHECKING:
    import nbformat


def create_load_test_report(
    test_paths: typing.Sequence[str],
    output_dir: str,
    metrics: typing.Sequence[str] | None = None,
) -> None:
    import os

    ipynb_path = os.path.join(output_dir, 'report.ipynb')
    _create_load_test_report_ipynb(
        test_paths=test_paths,
        output_path=ipynb_path,
        metrics=metrics,
    )

    _create_load_test_report_html(ipynb_path=ipynb_path)


def print_load_test_summary(test: flood.LoadTest) -> None:
    import toolstr

    parsed = flood.parse_test_data(test)
    rates = parsed['rates']
    durations = parsed['durations']
    vegeta_kwargs = parsed['vegeta_kwargs']

    toolstr.print_bullet(key='sample rates', value=rates, styles=flood.styles)
    if len(set(durations)) == 1:
        toolstr.print_bullet(
            key='sample duration',
            value=durations[0],
            styles=flood.styles,
        )
    else:
        toolstr.print_bullet(
            key='sample durations', value=durations, styles=flood.styles
        )
    if vegeta_kwargs is None or len(vegeta_kwargs) == 0:
        toolstr.print_bullet(key='extra args', value=None, styles=flood.styles)


def _create_load_test_report_html(
    ipynb_path: str,
) -> None:
    import subprocess

    cmd = 'python3 -m nbconvert --to html --execute ' + ipynb_path
    subprocess.call(cmd.split(' '))


def _create_load_test_report_ipynb(
    test_paths: typing.Sequence[str],
    output_path: str,
    metrics: typing.Sequence[str] | None = None,
) -> None:
    from flood.user_io import notebook_io

    if metrics is None:
        metrics = ['success', 'throughput', 'p50', 'p90', 'p99']

    test_paths_map = {}
    for test_path in test_paths:
        payload = flood.load_single_run_test_payload(test_path)
        name = payload['name']
        test_paths_map[name] = test_path

    inputs = {
        'test_paths': test_paths_map,
        'metrics': metrics,
        'test_names': list(test_paths_map.keys()),
    }

    notebook_io.create_notebook(
        cell_templates=_report_template_cells,
        output_path=output_path,
        inputs=inputs,
    )


_header_template = """# `flood` Load Test Report


{toc}

### Report Generation

*This report was generated by `flood`. See the `flood report --help` command for report generation options. This report can be executed as a Python notebook using the `.ipynb` version of this file.*"""  # noqa: E501


_toc_template = """### Contents
1. [Test Summary](#Test-Summary)
2. [Tests](#Tests)
{toc_lines}"""


def _create_header_cell(test_names: typing.Sequence[str]) -> str:
    toc = _create_test_toc(test_names=test_names)
    return _header_template.format(toc=toc)


def _create_test_toc(test_names: typing.Sequence[str]) -> str:
    line_template = '. [Test: {test_name}](#Test:-{test_name})\n'
    toc = ''
    for t, test_name in enumerate(test_names):
        toc += '    ' + str(t + 1) + line_template.format(test_name=test_name)
    toc = toc.rstrip()
    return _toc_template.format(toc_lines=toc)


def _create_parameters_cell(
    test_paths: typing.Mapping[str, str],
    metrics: typing.Sequence[str],
) -> str:
    parameters = '# parameters\n\n'

    parameters += 'test_paths = ' + (
        repr(test_paths)
        .replace(',', ',\n   ')
        .replace('{', '{\n    ')
        .replace('}', ',\n}')
    )

    parameters += '\n\nmetrics = ' + str(metrics)

    return parameters


def _create_test_chunks(
    test_names: typing.Sequence[str],
) -> typing.Sequence[nbformat.notebooknode.NotebookNode]:
    return [
        cell
        for test_name in test_names
        for cell in _create_test_chunk(test_name)
    ]


def _create_test_chunk(
    test_name: str,
) -> typing.Sequence[nbformat.notebooknode.NotebookNode]:
    from flood.user_io import notebook_io

    return notebook_io.create_cells(
        cell_templates=_test_template_cells,
        inputs={'test_name': test_name},
    )


if typing.TYPE_CHECKING:
    from flood.user_io import notebook_io

_report_template_cells: notebook_io.NotebookTemplate = [
    {
        # header
        'type': 'markdown',
        'f': _create_header_cell,
        'inputs': ['test_names'],
    },
    {
        # imports
        'type': 'code',
        'content': """
            import IPython
            import polars as pl
            import toolstr
            import tooltime

            import flood

            flood.styles = {{}}
        """,
        'inputs': [],
    },
    {
        # parameters
        'type': 'code',
        'f': _create_parameters_cell,
        'inputs': ['test_paths', 'metrics'],
    },
    {
        # load data
        'type': 'code',
        'content': """
            # load data

            test_payloads = {{
                test_name: flood.load_single_run_test_payload(test_path)
                for test_name, test_path in test_paths.items()
            }}

            results_payloads = {{
                test_name: flood.load_single_run_results_payload(output_dir=test_path)
                for test_name, test_path in test_paths.items()
            }}
        """,  # noqa: E501
        'inputs': [],
    },
    {
        # Test summary header
        'type': 'markdown',
        'content': '# Test Summary',
        'inputs': [],
    },
    {
        # test list
        'type': 'code',
        'content': """
            # test list

            toolstr.print_text_box('Tests')
            for t, test_name in enumerate(results_payloads.keys()):
                print(str(t + 1) + '.', test_name)
        """,
        'inputs': [],
    },
    {
        # test durations
        'type': 'code',
        'content': """
            # test durations

            time_per_test = {{}}
            time_per_condition = {{}}

            for test_name in results_payloads:
                results = results_payloads[test_name]["results"]
                time_per_test[test_name] = 0
                for condition_name in results.keys():
                    time_per_condition.setdefault(condition_name, 0)
                    time = sum(results[condition_name]["actual_duration"]) + sum(
                        results[condition_name]["final_wait_time"]
                    )
                    time_per_test[test_name] += time
                    time_per_condition[condition_name] += time

            toolstr.print_text_box('Total time')
            toolstr.print(tooltime.timelength_to_phrase(int(sum(time_per_test.values()))))
            print()

            toolstr.print_text_box('Total time per test')
            rows = list(time_per_test.items())
            toolstr.print_table(rows, labels=['test', 'time (s)'])

            toolstr.print_text_box('Total time per condition')
            rows = list(time_per_condition.items())
            toolstr.print_table(rows, labels=['condition', 'time (s)'])
        """,  # noqa: E501
        'inputs': [],
    },
    {
        # Tests header
        'type': 'markdown',
        'content': '# Tests',
        'inputs': [],
    },
    {
        # Tests chunks
        'type': 'chunk',
        'f': _create_test_chunks,
        'inputs': ['test_names'],
    },
]


_test_template_cells: notebook_io.NotebookTemplate = [
    {
        # test header
        'type': 'markdown',
        'content': '# Test: {test_name}',
        'inputs': ['test_name'],
    },
    {
        # load test results
        'type': 'code',
        'content': """
            # load test results

            test_name = '{test_name}'
            results_payload = results_payloads[test_name]
            results = results_payload['results']
        """,
        'inputs': ['test_name'],
    },
    {
        # show test metadata
        'type': 'code',
        'content': """
            # show test metadata

            toolstr.print_text_box(test_name + ' parameters')
            flood.print_load_test_summary(results_payload['test'])
            toolstr.print('- nodes tested:')
            nodes_df = pl.from_records(list(results_payload['nodes'].values()))
            toolstr.print_dataframe_as_table(nodes_df)
        """,
        'inputs': [],
    },
    {
        # show result tables
        'type': 'code',
        'content': """
            # show result tables

            flood.print_metric_tables(results, metrics=metrics, comparison=True)
        """,
        'inputs': [],
    },
    {
        # show result figures
        'type': 'code',
        'content': """
            # show result figures

            colors = flood.get_nodes_plot_colors(nodes=results_payload['nodes'])
            flood.plot_load_test_results(
                test_name=test_name,
                outputs=results,
                latency_yscale_log=True,
                colors=colors,
            )
        """,
        'inputs': [],
    },
    {
        # show errors
        'type': 'code',
        'content': """
            # show errors

            toolstr.print_text_box('Error messages present in each test')
            unique_errors = {{}}
            for n, name in enumerate(results.keys()):
                unique_errors.setdefault(name, set())
                unique_errors[name] |= {{
                    error for error_list in results[name]['errors'] for error in error_list
                }}
                print(name)
                for error in unique_errors[name]:
                    print('-', error)
                if n != len(results) - 1:
                    print()
        """,  # noqa: E501
        'inputs': [],
    },
    {
        # show complete results
        'type': 'code',
        'content': """
            # show complete results

            for name in results.keys():
                toolstr.print_text_box(name + " Complete Results")
                df = pl.DataFrame(results[name])
                df = df.drop(
                    "status_codes",
                    "errors",
                    "first_request_timestamp",
                    "last_request_timestamp",
                    "last_response_timestamp",
                )
                IPython.display.display(df)
        """,
        'inputs': [],
    },
]
