# SPDX-License-Identifier: BSD-3-Clause
# Copyright(c) 2010-2021 Intel Corporation
# Copyright(c) 2022-2023 PANTHEON.tech s.r.o.
# Copyright(c) 2022 University of New Hampshire

import argparse
import os
from collections.abc import Callable, Iterable, Sequence
from dataclasses import dataclass
from pathlib import Path
from typing import Any, TypeVar

from .utils import DPDKGitTarball

_T = TypeVar("_T")


def _env_arg(env_var: str) -> Any:
    class _EnvironmentArgument(argparse.Action):
        def __init__(
            self,
            option_strings: Sequence[str],
            dest: str,
            nargs: str | int | None = None,
            const: str | None = None,
            default: str = None,
            type: Callable[[str], _T | argparse.FileType | None] = None,
            choices: Iterable[_T] | None = None,
            required: bool = False,
            help: str | None = None,
            metavar: str | tuple[str, ...] | None = None,
        ) -> None:
            env_var_value = os.environ.get(env_var)
            default = env_var_value or default
            super(_EnvironmentArgument, self).__init__(
                option_strings,
                dest,
                nargs=nargs,
                const=const,
                default=default,
                type=type,
                choices=choices,
                required=required,
                help=help,
                metavar=metavar,
            )

        def __call__(
            self,
            parser: argparse.ArgumentParser,
            namespace: argparse.Namespace,
            values: Any,
            option_string: str = None,
        ) -> None:
            setattr(namespace, self.dest, values)

    return _EnvironmentArgument


@dataclass(slots=True, frozen=True)
class _Settings:
    config_file_path: str
    output_dir: str
    timeout: float
    verbose: bool
    skip_setup: bool
    dpdk_tarball_path: Path
    compile_timeout: float
    test_cases: list
    re_run: int


def _get_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        description="Run DPDK test suites. All options may be specified with the environment "
        "variables provided in brackets. Command line arguments have higher priority.",
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
    )

    parser.add_argument(
        "--config-file",
        action=_env_arg("DTS_CFG_FILE"),
        default="conf.yaml",
        help="[DTS_CFG_FILE] configuration file that describes the test cases, SUTs and targets.",
    )

    parser.add_argument(
        "--output-dir",
        "--output",
        action=_env_arg("DTS_OUTPUT_DIR"),
        default="output",
        help="[DTS_OUTPUT_DIR] Output directory where dts logs and results are saved.",
    )

    parser.add_argument(
        "-t",
        "--timeout",
        action=_env_arg("DTS_TIMEOUT"),
        default=15,
        type=float,
        help="[DTS_TIMEOUT] The default timeout for all DTS operations except for compiling DPDK.",
    )

    parser.add_argument(
        "-v",
        "--verbose",
        action=_env_arg("DTS_VERBOSE"),
        default="N",
        help="[DTS_VERBOSE] Set to 'Y' to enable verbose output, logging all messages "
        "to the console.",
    )

    parser.add_argument(
        "-s",
        "--skip-setup",
        action=_env_arg("DTS_SKIP_SETUP"),
        default="N",
        help="[DTS_SKIP_SETUP] Set to 'Y' to skip all setup steps on SUT and TG nodes.",
    )

    parser.add_argument(
        "--tarball",
        "--snapshot",
        "--git-ref",
        action=_env_arg("DTS_DPDK_TARBALL"),
        default="dpdk.tar.xz",
        type=Path,
        help="[DTS_DPDK_TARBALL] Path to DPDK source code tarball or a git commit ID, "
        "tag ID or tree ID to test. To test local changes, first commit them, "
        "then use the commit ID with this option.",
    )

    parser.add_argument(
        "--compile-timeout",
        action=_env_arg("DTS_COMPILE_TIMEOUT"),
        default=1200,
        type=float,
        help="[DTS_COMPILE_TIMEOUT] The timeout for compiling DPDK.",
    )

    parser.add_argument(
        "--test-cases",
        action=_env_arg("DTS_TESTCASES"),
        default="",
        help="[DTS_TESTCASES] Comma-separated list of test cases to execute. "
        "Unknown test cases will be silently ignored.",
    )

    parser.add_argument(
        "--re-run",
        "--re_run",
        action=_env_arg("DTS_RERUN"),
        default=0,
        type=int,
        help="[DTS_RERUN] Re-run each test case the specified amount of times "
        "if a test failure occurs",
    )

    return parser


def _get_settings() -> _Settings:
    parsed_args = _get_parser().parse_args()
    return _Settings(
        config_file_path=parsed_args.config_file,
        output_dir=parsed_args.output_dir,
        timeout=parsed_args.timeout,
        verbose=(parsed_args.verbose == "Y"),
        skip_setup=(parsed_args.skip_setup == "Y"),
        dpdk_tarball_path=Path(DPDKGitTarball(parsed_args.tarball, parsed_args.output_dir))
        if not os.path.exists(parsed_args.tarball)
        else Path(parsed_args.tarball),
        compile_timeout=parsed_args.compile_timeout,
        test_cases=parsed_args.test_cases.split(",") if parsed_args.test_cases else [],
        re_run=parsed_args.re_run,
    )


SETTINGS: _Settings = _get_settings()
