"""Create, run or investigate regression checks."""
import pickle
import socket

import click
import numpy as np

from respy.config import TEST_RESOURCES_DIR
from respy.config import TOL_REGRESSION_TESTS
from respy.tests.random_model import generate_random_model
from respy.tests.test_regression import compute_log_likelihood
from respy.tests.test_regression import load_regression_tests

CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]}


def _prepare_message(idx_failures):
    hostname = socket.gethostname()
    subject = " respy: Regression Testing"
    if idx_failures:
        message = (
            f"Failure during regression testing @{hostname} for test(s): "
            f"{idx_failures}."
        )
    else:
        message = f"Regression testing is completed on @{hostname}."

    return subject, message


def run_regression_tests(n_tests, strict, notification):
    """Run regression tests.

    Parameters
    ----------
    n_tests : int
        Number of tests to run. If None, all are run.
    strict : bool, default False
        Early failure on error.
    notification : bool, default True
        Send notification with test report.

    """
    tests = load_regression_tests()
    tests = tests[: n_tests + 1]

    results = [_check_single(test, strict) for test in tests]
    idx_failures = [i for i, x in enumerate(results) if not x]

    if idx_failures:
        click.secho(f"Failures: {idx_failures}", fg="red")
    else:
        click.secho(f"Tests succeeded.", fg="green")

    subject, message = _prepare_message(idx_failures)

    if notification:
        from development.testing.notifications import send_notification

        send_notification(subject, message)


def create_regression_tests(n_tests, save):
    """Create a regression vault.

    Parameters
    ----------
    n_tests : int
        How many tests are in the vault.
    save : bool, default True
        Flag for saving new tests to disk.

    """
    tests = [_create_single(i) for i in range(n_tests)]

    if save:
        with open(TEST_RESOURCES_DIR / "regression_vault.pickle", "wb") as p:
            pickle.dump(tests, p)


def investigate_regression_test(idx):
    """Investigate regression tests."""
    tests = load_regression_tests()
    params, options, exp_val = tests[idx]

    crit_val = compute_log_likelihood(params, options)

    assert np.isclose(
        crit_val, exp_val, rtol=TOL_REGRESSION_TESTS, atol=TOL_REGRESSION_TESTS
    )


def _check_single(test, strict):
    """Check a single test."""
    params, options, exp_val = test

    try:
        crit_val = compute_log_likelihood(params, options)
        is_success = np.isclose(
            crit_val, exp_val, rtol=TOL_REGRESSION_TESTS, atol=TOL_REGRESSION_TESTS
        )
    except Exception:
        is_success = False

    if strict is True:
        assert is_success, "Failed regression test."

    return is_success


def _create_single(idx):
    """Create a single test."""
    np.random.seed(idx)

    params, options = generate_random_model()

    crit_val = compute_log_likelihood(params, options)

    if not isinstance(crit_val, float):
        raise AssertionError(" ... value of criterion function too large.")

    return params, options, crit_val


@click.group(context_settings=CONTEXT_SETTINGS)
def cli():
    """CLI manager for regression tests."""
    pass


@cli.command()
@click.argument("number_of_tests", type=int)
@click.option("--strict", is_flag=True, help="Immediate termination on failure.")
@click.option("--notification/--no-notification", default=True, help="Send report.")
def run(number_of_tests, strict, notification):
    """Run a number of regression tests."""
    run_regression_tests(
        n_tests=number_of_tests, strict=strict, notification=notification
    )


@cli.command()
@click.argument("number_of_test", type=int)
def investigate(number_of_test):
    """Investigate a single regression test."""
    investigate_regression_test(number_of_test)


@cli.command()
@click.argument("number_of_tests", type=int)
@click.option("--save/--no-save", default=True, help="Saves new tests on disk.")
def create(number_of_tests, save):
    """Create a new collection of regression tests."""
    create_regression_tests(n_tests=number_of_tests, save=save)


if __name__ == "__main__":
    cli()