# Copyright (c) 2024, Janus Heide.
# All rights reserved.
#
# Distributed under the "BSD 3-Clause License", see LICENSE.txt.

from __future__ import annotations

import asyncio
from argparse import (
    ArgumentDefaultsHelpFormatter, ArgumentParser, FileType, Namespace,
)
from importlib.metadata import version
from logging import basicConfig, getLevelName, getLogger
from pathlib import Path
from subprocess import CalledProcessError, run

from packaging.requirements import Requirement

try:
    from tomllib import load  # type: ignore
except ModuleNotFoundError:
    from tomli import load  # type: ignore


logger = getLogger(__name__)


def optional_dependencies(optional_dep: dict) -> list[str]:
    """Find all package names in project optional dependencies."""
    return [Requirement(d).name for deps in optional_dep.values() for d in deps]


def run_linters(dependencies: list[str]) -> None:
    """Run any of the following linters found in the dependencies list,
    and exit with and error if any of them fails.
        1. licensecheck
        2. isort
        3. ruff
        4. mypy
    """
    run_failed = False

    if "licensecheck" in dependencies:
        logger.info("Running licensecheck")
        try:
            run(["licensecheck", "--zero"], shell=False, check=True)
        except CalledProcessError:
            run_failed = True

    if "isort" in dependencies:
        logger.info("Running isort")
        try:
            run(["isort", "."], shell=False, check=True)
        except CalledProcessError:
            run_failed = True

    if "ruff" in dependencies:
        logger.info("Running ruff check")
        try:
            run(["ruff", "check"], shell=False, check=True)
        except CalledProcessError:
            run_failed = True

    if "mypy" in dependencies:
        logger.info("Running mypy")
        try:
            run(["mypy", "."], shell=False, check=True)
        except CalledProcessError:
            run_failed = True

    if run_failed:
        exit(1)


def parse_arguments() -> Namespace:
    """Parse arguments."""
    parser = ArgumentParser(
        description="Update Python Project Dependencies.",
        formatter_class=ArgumentDefaultsHelpFormatter,
    )

    parser.add_argument(
        "-i",
        "--infile",
        nargs="*",
        default="pyproject.toml",
        type=FileType("rb"),
        help="Path to input file",
    )

    parser.add_argument(
        "--log-level",
        default="INFO",
        choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
        help="Logging level.",
    )

    parser.add_argument(
        "--log-file", type=Path, help="Pipe loggining to file instead of stdout."
    )

    parser.add_argument("-v", "--version", action="version", version=version("brundle"))

    return parser.parse_args()


async def main(*, infile: FileType, log_file: Path, log_level: str) -> None:
    """Main."""
    basicConfig(
        filename=log_file,
        level=getLevelName(log_level),
        format="%(levelname)s: %(message)s",
    )

    data = load(infile) #type: ignore
    try:
        project = data["project"]
        try:
            run_linters(optional_dependencies(project["optional-dependencies"]))
        except KeyError:
            logger.exception(f"Could not find 'optional-dependencies' in: {infile}")
            exit(1)

    except KeyError:
        logger.critical(f"No project section in input file: {infile}")
        exit(1)

def main_cli() -> None:
    """Main."""
    args = parse_arguments()
    asyncio.run(main(**vars(args)))


if __name__ == "__main__":
    main_cli()
