"""CLI tests."""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING
from unittest import mock

import pytest

from nitpick import __version__
from nitpick.constants import (
    CONFIG_KEY_IGNORE_STYLES,
    CONFIG_KEY_STYLE,
    CONFIG_TOOL_NITPICK_KEY,
    DOT_NITPICK_TOML,
    PYTHON_PYPROJECT_TOML,
    EmojiEnum,
)
from tests.helpers import BLANK_LINE, OSAgnosticPaths, ProjectMock

if TYPE_CHECKING:
    from collections.abc import Generator


def test_simple_error(tmp_path: Path) -> None:
    """A simple error on the CLI."""
    project = (
        ProjectMock(tmp_path)
        .style(
            """
            ["pyproject.toml".tool.black]
            line-length = 100
            """
        )
        .pyproject_toml(
            """
            [tool.blabla]
            something = 22
            """
        )
    )

    project.cli_run(
        f"""
        {project.root_dir / "pyproject.toml"!s}:1: NIP318  has missing values:
        [tool.black]
        line-length = 100
        """
    )


def test_missing_style_and_suggest_option(tmp_path: Path) -> None:
    """Print error if both style and --suggest options are missing."""
    ProjectMock(tmp_path).cli_init(
        f"Nothing to do. {EmojiEnum.SLEEPY_FACE.value} Either pass at least one style URL"
        " or use --suggest to add styles based on the files in the project root"
        " (you can do both at the same time)."
    )


@pytest.fixture
def style_dir_with_types(shared_datadir: Path) -> Generator[Path, None, None]:
    """A mocked directory with style files organised into "identify" subdirs."""
    with mock.patch("nitpick.style.builtin_resources_root") as mock_builtin_resources_root:
        mock_builtin_resources_root.return_value = Path(shared_datadir / "typed-style-dir")
        yield mock_builtin_resources_root


@pytest.mark.parametrize(
    ("pyproject_toml", "fix", "before", "after"),
    [
        (None, True, "", ""),
        # tomlkit keeps the blank line at the top when the file exists and is empty...
        ("", True, BLANK_LINE, ""),
        # Existing table with other keys but missing the "style" key
        (f'[{CONFIG_TOOL_NITPICK_KEY}]\ncache = "never"', True, "", f'{BLANK_LINE}cache = "never"'),
    ],
)
def test_create_update_tool_nitpick_table_on_config_files(
    style_dir_with_types: Path,
    tmp_path: Path,
    pyproject_toml: str | None,
    fix: bool,
    before: str,
    after: str,
) -> None:
    """If no config file is found, create a basic .nitpick.toml."""
    assert style_dir_with_types
    project = ProjectMock(tmp_path)
    if pyproject_toml is None:
        config_file = DOT_NITPICK_TOML
    else:
        config_file = PYTHON_PYPROJECT_TOML
        project.pyproject_toml(pyproject_toml)
    project.cli_init(
        f"""
        New styles:
        - py://data/typed-style-dir/any/editorconfig
        - py://data/typed-style-dir/python/black
        The [{CONFIG_TOOL_NITPICK_KEY}] table was updated in {config_file!r}: 2 styles appended. {EmojiEnum.STAR_CAKE.value}
        """,
        fix=fix,
        suggest=True,
    )
    if fix:
        project.assert_file_contents(
            config_file,
            f"""
            {before}[{CONFIG_TOOL_NITPICK_KEY}]{after}
            # nitpick-start (auto-generated by "nitpick init --suggest" {__version__})
            # Styles added to the Nitpick Style Library will be appended to the end of the 'style' key.
            # If you don't want a style, move it to the '{CONFIG_KEY_IGNORE_STYLES}' key.
            # nitpick-end
            style = [
              "py://data/typed-style-dir/any/editorconfig",
              "py://data/typed-style-dir/python/black",]
            {CONFIG_KEY_IGNORE_STYLES} = []
            """,
        )
    else:
        assert not (tmp_path / DOT_NITPICK_TOML).exists()


@pytest.mark.parametrize(
    ("fix", "footer"),
    [
        (
            True,
            f"The [{CONFIG_TOOL_NITPICK_KEY}] table was updated in {DOT_NITPICK_TOML!r}: 1 style appended. {EmojiEnum.STAR_CAKE.value}",
        ),
        (
            False,
            f"Use --fix to append 1 style to the [{CONFIG_TOOL_NITPICK_KEY}] table in the config file '{DOT_NITPICK_TOML}'.",
        ),
    ],
)
def test_create_the_ignored_styles_array_only_when_suggesting_styles(tmp_path: Path, fix: bool, footer: str) -> None:
    """Create the ignored styles array only when suggesting styles."""
    project = ProjectMock(tmp_path)
    project.cli_init(
        f"""
        New styles:
        - my-style.toml
        {footer}
        """,
        fix=fix,
        style_urls=["my-style.toml"],
    )
    if fix:
        project.assert_file_contents(
            DOT_NITPICK_TOML,
            f"""
            [{CONFIG_TOOL_NITPICK_KEY}]
            style = [
              "my-style.toml",]
            """,
        )
    else:
        assert not (tmp_path / DOT_NITPICK_TOML).exists()


@pytest.mark.parametrize(
    ("fix", "key"),
    [
        (True, CONFIG_KEY_STYLE),
        (False, CONFIG_KEY_IGNORE_STYLES),
    ],
)
def test_dont_add_again_an_existing_nor_ignored_style(
    tmp_path: Path,
    fix: bool,
    key: str,
) -> None:
    """Don't add again an existing nor ignored style."""
    unchanged = f"""
        [{CONFIG_TOOL_NITPICK_KEY}]
        {key} = ["my-style.toml"]
    """
    ProjectMock(tmp_path).pyproject_toml(unchanged).cli_init(
        f"All done! {EmojiEnum.STAR_CAKE.value} [{CONFIG_TOOL_NITPICK_KEY}]"
        f" table left unchanged in '{PYTHON_PYPROJECT_TOML}'",
        fix=fix,
        style_urls=["my-style.toml"],
        exit_code=0,
    ).assert_file_contents(
        PYTHON_PYPROJECT_TOML,
        unchanged,
    )


def test_added_style_should_be_the_first(
    style_dir_with_types: Path,
    tmp_path: Path,
) -> None:
    """The style added from the CLI should be the first, before the suggested styles."""
    assert style_dir_with_types
    project = ProjectMock(tmp_path)
    project.cli_init(
        f"""
        New styles:
        - my-style.toml
        - py://data/typed-style-dir/any/editorconfig
        - py://data/typed-style-dir/python/black
        The [{CONFIG_TOOL_NITPICK_KEY}] table was updated in {DOT_NITPICK_TOML!r}: 3 styles appended. {EmojiEnum.STAR_CAKE.value}
        """,
        fix=True,
        suggest=True,
        style_urls=["my-style.toml"],
    )
    project.assert_file_contents(
        DOT_NITPICK_TOML,
        f"""
        [{CONFIG_TOOL_NITPICK_KEY}]
        # nitpick-start (auto-generated by "nitpick init --suggest" {__version__})
        # Styles added to the Nitpick Style Library will be appended to the end of the 'style' key.
        # If you don't want a style, move it to the '{CONFIG_KEY_IGNORE_STYLES}' key.
        # nitpick-end
        style = [
          "my-style.toml",
          "py://data/typed-style-dir/any/editorconfig",
          "py://data/typed-style-dir/python/black",]
        {CONFIG_KEY_IGNORE_STYLES} = []
        """,
    )


@pytest.mark.parametrize(
    ("fix", "footer"),
    [
        (
            True,
            f"The [{CONFIG_TOOL_NITPICK_KEY}] table was updated in {DOT_NITPICK_TOML!r}: 3 styles appended. {EmojiEnum.STAR_CAKE.value}",
        ),
        (
            False,
            f"Use --fix to append 3 styles to the [{CONFIG_TOOL_NITPICK_KEY}] table in the config file '{DOT_NITPICK_TOML}'.",
        ),
    ],
)
def test_use_custom_library_dir(shared_datadir: Path, tmp_path: Path, fix: bool, footer: str) -> None:
    """Use a custom library directory."""
    project = ProjectMock(tmp_path).save_file("README.md", "Hello")
    agnostic_paths = OSAgnosticPaths(
        shared_datadir / "typed-style-dir", "any/editorconfig", "markdown/markdownlint", "python/black"
    )
    project.cli_init(
        f"""
        New styles:
        {agnostic_paths.as_bullets(2)}
        {footer}
        """,
        fix=fix,
        library=shared_datadir / "typed-style-dir",
    )
    if fix:
        project.assert_file_contents(
            DOT_NITPICK_TOML,
            f"""
            [{CONFIG_TOOL_NITPICK_KEY}]
            # nitpick-start (auto-generated by "nitpick init --suggest" {__version__})
            # Styles added to the Nitpick Style Library will be appended to the end of the 'style' key.
            # If you don't want a style, move it to the '{CONFIG_KEY_IGNORE_STYLES}' key.
            # nitpick-end
            style = [
            {agnostic_paths.as_toml_items(3)}]
            {CONFIG_KEY_IGNORE_STYLES} = []
            """,
        )
    else:
        assert not (tmp_path / DOT_NITPICK_TOML).exists()
