# SPDX-FileCopyrightText: Copyright © 2025 Idiap Research Institute <contact@idiap.ch>
#
# SPDX-License-Identifier: GPL-3.0-or-later

import pathlib
import re

from click.testing import CliRunner

from fairical.scripts.cli import cli


def str_counter(substr: str, s: str) -> int:
    """Count number of occurences of regular expression ``str`` on ``s``.

    Parameters
    ----------
    substr
        String or regular expression to search for in ``s``.
    s
        String where to search for ``substr``, that may include new-line characters.

    Returns
    -------
        The count on the number of occurences of ``substr`` on ``s``.
    """

    return sum(1 for _ in re.finditer(substr, s, re.MULTILINE))


def test_cli_help() -> None:
    runner = CliRunner()
    result = runner.invoke(cli, ["--help"])
    assert result.exit_code == 0
    assert result.output.startswith("Usage:")


def test_cli_solve_help() -> None:
    runner = CliRunner()
    result = runner.invoke(cli, ["solve", "--help"])
    assert result.exit_code == 0
    assert result.output.startswith("Usage:")


def test_cli_evaluate_help() -> None:
    runner = CliRunner()
    result = runner.invoke(cli, ["evaluate", "--help"])
    assert result.exit_code == 0
    assert result.output.startswith("Usage:")


def test_cli_pareto_help() -> None:
    runner = CliRunner()
    result = runner.invoke(cli, ["pareto", "--help"])
    assert result.exit_code == 0
    assert result.output.startswith("Usage:")


def test_cli_solve(datadir: pathlib.Path, tmp_path: pathlib.Path, caplog):
    runner = CliRunner()

    with caplog.at_level("INFO", logger="fairical"):
        result = runner.invoke(
            cli,
            [
                "solve",
                "-vv",
                "--metric=acc",
                "--metric=eod+race",
                "--metric=eod+gender",
                str(datadir / "data" / "empirical" / "system_1.json"),
                str(datadir / "data" / "empirical" / "system_2.json"),
                "--output-path",
                str(tmp_path),
                "--thresholds=3",
            ],
        )

    assert result.exit_code == 0

    assert (
        str_counter("Calculating solutions \\*a posteriori\\* for system", caplog.text)
        == 2
    )
    assert str_counter("Evaluating subsystem", caplog.text) == 50
    assert str_counter("Deduplication reduced solutions", caplog.text) == 2
    assert str_counter("Saving only non-dominated solutions", caplog.text) == 0
    assert str_counter("Saving only dominated solutions", caplog.text) == 0
    assert str_counter(f"Saved `{str(tmp_path)}.*`$", caplog.text) == 2

    assert pathlib.Path(tmp_path / "system_1.json").exists()
    assert pathlib.Path(tmp_path / "system_2.json").exists()


def test_cli_solve_a_priori(datadir: pathlib.Path, tmp_path: pathlib.Path, caplog):
    runner = CliRunner()

    with caplog.at_level("INFO", logger="fairical"):
        result = runner.invoke(
            cli,
            [
                "solve",
                "-vv",
                "--metric=acc",
                "--metric=eod+race",
                "--metric=eod+gender",
                str(datadir / "data" / "empirical" / "system_1.json")
                + "@"
                + str(datadir / "data" / "empirical" / "system_2_nds.json"),
                "--output-path",
                str(tmp_path),
                "--thresholds=3",
            ],
        )

    assert result.exit_code == 0

    assert pathlib.Path(tmp_path / "system_1@system_2_nds.json").exists()

    assert (
        str_counter("Calculating solutions \\*a priori\\* for system", caplog.text) == 1
    )
    assert str_counter("Evaluating 9 operating modes", caplog.text) == 1
    assert str_counter("Deduplication reduced solutions", caplog.text) == 1
    assert str_counter("Saving only non-dominated solutions", caplog.text) == 0
    assert str_counter("Saving only dominated solutions", caplog.text) == 0
    assert str_counter(f"Saved `{str(tmp_path)}.*`$", caplog.text) == 1


def test_cli_evaluate(datadir: pathlib.Path, tmp_path: pathlib.Path, caplog):
    runner = CliRunner()

    radar_pdf = tmp_path / "radar.pdf"

    with caplog.at_level("INFO", logger="fairical"):
        result = runner.invoke(
            cli,
            [
                "evaluate",
                "-vv",
                str(datadir / "data" / "uc-2" / "system_1.json"),
                str(datadir / "data" / "uc-2" / "system_2.json"),
                "--deduplicate",
                "--radar",
                str(radar_pdf),
            ],
        )

    assert result.exit_code == 0
    assert radar_pdf.exists()

    assert (
        str_counter(
            r"^\s*system_1\s*0.12\s*0.50\s*1.00\s*0.00\s*0.24\s*0.12$", result.output
        )
        == 1
    )
    assert (
        str_counter(
            r"^\s*system_2\s*1.00\s*0.89\s*1.00\s*0.26\s*0.09\s*0.43$", result.output
        )
        == 1
    )

    assert str_counter("Loading solutions for system", caplog.text) == 2
    assert str_counter("Deduplication reduced solutions", caplog.text) == 2
    assert str_counter("Non-dominated/Dominated solutions", caplog.text) == 2


def test_cli_evaluate_nodedup(datadir: pathlib.Path, tmp_path: pathlib.Path, caplog):
    runner = CliRunner()

    radar_pdf = tmp_path / "radar.pdf"

    with caplog.at_level("INFO", logger="fairical"):
        result = runner.invoke(
            cli,
            [
                "evaluate",
                "-vv",
                str(datadir / "data" / "uc-2" / "system_1.json"),
                str(datadir / "data" / "uc-2" / "system_2.json"),
                "--no-deduplicate",
            ],
        )

    assert result.exit_code == 0
    assert not radar_pdf.exists()  # should not exist, as no opt given

    assert (
        str_counter(
            r"^\s*system_1\s*0.12\s*0.04\s*1.00\s*0.00\s*0.24\s*0.02$", result.output
        )
        == 1
    )
    assert (
        str_counter(
            r"^\s*system_2\s*1.00\s*0.32\s*1.00\s*0.26\s*0.09\s*0.20$", result.output
        )
        == 1
    )

    assert str_counter("Loading solutions for system.*", caplog.text) == 2
    assert str_counter("Non-dominated/Dominated solutions", caplog.text) == 2


def test_cli_pareto_3d(datadir: pathlib.Path, tmp_path: pathlib.Path, caplog):
    runner = CliRunner()

    with caplog.at_level("INFO", logger="fairical"):
        result = runner.invoke(
            cli,
            [
                "pareto",
                "-vv",
                str(datadir / "data" / "uc-3" / "system_1.json"),
                str(datadir / "data" / "uc-3" / "system_2.json"),
                "--plot",
                "--output-path",
                str(tmp_path),
            ],
        )

    expected_pdf_path = tmp_path / "pareto.pdf"
    assert result.exit_code == 0
    assert pathlib.Path(expected_pdf_path).exists()

    assert str_counter("Loading solutions for system", caplog.text) == 2
    assert str_counter("Deduplication reduced solutions", caplog.text) == 2
    assert str_counter("Non-dominated/Dominated solutions", caplog.text) == 2
    assert str_counter(f"Saving plot at `{expected_pdf_path}`...", caplog.text) == 1
