"""Tests for apps:link:add / apps:link:remove — CLI + project file."""

from __future__ import annotations

import pathlib

import yaml
from click.testing import CliRunner

from kuberoku.branding import PROJECT_FILE_NAME
from kuberoku.cli.main import cli
from kuberoku.config.project import (
    read_project_file,
    remove_alias,
    remove_project_file,
    resolve_project_link,
    write_project_file,
)


class TestProjectFileRead:
    """Tests for config/project.py — read/write/resolve."""

    def test_write_simple(self, tmp_path: pathlib.Path) -> None:
        path = write_project_file("myapi", directory=tmp_path)
        assert path.name == PROJECT_FILE_NAME
        data = yaml.safe_load(path.read_text())
        assert data["app"] == "myapi"

    def test_write_with_cluster(self, tmp_path: pathlib.Path) -> None:
        write_project_file("myapi", cluster="production", directory=tmp_path)
        data = yaml.safe_load((tmp_path / PROJECT_FILE_NAME).read_text())
        assert data["cluster"] == "production"

    def test_write_with_alias(self, tmp_path: pathlib.Path) -> None:
        write_project_file("myapi-staging", alias="staging", directory=tmp_path)
        data = yaml.safe_load((tmp_path / PROJECT_FILE_NAME).read_text())
        assert data["default"] == "staging"
        assert data["environments"]["staging"]["app"] == "myapi-staging"

    def test_write_multiple_aliases(self, tmp_path: pathlib.Path) -> None:
        write_project_file("myapi-dev", alias="dev", directory=tmp_path)
        write_project_file(
            "myapi-staging", alias="staging", cluster="staging-cluster", directory=tmp_path
        )
        data = yaml.safe_load((tmp_path / PROJECT_FILE_NAME).read_text())
        assert data["default"] == "dev"
        assert data["environments"]["dev"]["app"] == "myapi-dev"
        assert data["environments"]["staging"]["app"] == "myapi-staging"
        assert data["environments"]["staging"]["cluster"] == "staging-cluster"

    def test_resolve_simple(self, tmp_path: pathlib.Path) -> None:
        write_project_file("myapi", directory=tmp_path)
        link = resolve_project_link(start_dir=tmp_path)
        assert link is not None
        assert link.app == "myapi"

    def test_resolve_default_alias(self, tmp_path: pathlib.Path) -> None:
        write_project_file("myapi-dev", alias="dev", directory=tmp_path)
        link = resolve_project_link(start_dir=tmp_path)
        assert link is not None
        assert link.app == "myapi-dev"

    def test_resolve_specific_alias(self, tmp_path: pathlib.Path) -> None:
        write_project_file("myapi-dev", alias="dev", directory=tmp_path)
        write_project_file("myapi-prod", alias="prod", cluster="production", directory=tmp_path)
        link = resolve_project_link(alias="prod", start_dir=tmp_path)
        assert link is not None
        assert link.app == "myapi-prod"
        assert link.cluster == "production"

    def test_resolve_unknown_alias(self, tmp_path: pathlib.Path) -> None:
        write_project_file("myapi-dev", alias="dev", directory=tmp_path)
        link = resolve_project_link(alias="unknown", start_dir=tmp_path)
        assert link is None

    def test_resolve_no_file(self, tmp_path: pathlib.Path) -> None:
        link = resolve_project_link(start_dir=tmp_path)
        assert link is None

    def test_read_walks_up(self, tmp_path: pathlib.Path) -> None:
        write_project_file("myapi", directory=tmp_path)
        subdir = tmp_path / "src" / "deep"
        subdir.mkdir(parents=True)
        data = read_project_file(start_dir=subdir)
        assert data is not None
        assert data["app"] == "myapi"

    def test_remove_file(self, tmp_path: pathlib.Path) -> None:
        write_project_file("myapi", directory=tmp_path)
        assert remove_project_file(directory=tmp_path) is True
        assert not (tmp_path / PROJECT_FILE_NAME).exists()

    def test_remove_file_not_exists(self, tmp_path: pathlib.Path) -> None:
        assert remove_project_file(directory=tmp_path) is False

    def test_remove_alias_removes_entry(self, tmp_path: pathlib.Path) -> None:
        write_project_file("myapi-dev", alias="dev", directory=tmp_path)
        write_project_file("myapi-prod", alias="prod", directory=tmp_path)
        assert remove_alias("dev", directory=tmp_path) is True
        data = yaml.safe_load((tmp_path / PROJECT_FILE_NAME).read_text())
        assert "dev" not in data["environments"]
        assert "prod" in data["environments"]

    def test_remove_last_alias_removes_file(self, tmp_path: pathlib.Path) -> None:
        write_project_file("myapi-dev", alias="dev", directory=tmp_path)
        assert remove_alias("dev", directory=tmp_path) is True
        assert not (tmp_path / PROJECT_FILE_NAME).exists()

    def test_remove_nonexistent_alias(self, tmp_path: pathlib.Path) -> None:
        write_project_file("myapi-dev", alias="dev", directory=tmp_path)
        assert remove_alias("nope", directory=tmp_path) is False

    def test_remove_alias_no_file(self, tmp_path: pathlib.Path) -> None:
        assert remove_alias("dev", directory=tmp_path) is False

    def test_read_empty_file(self, tmp_path: pathlib.Path) -> None:
        (tmp_path / PROJECT_FILE_NAME).write_text("")
        assert read_project_file(start_dir=tmp_path) is None

    def test_resolve_empty_environments(self, tmp_path: pathlib.Path) -> None:
        (tmp_path / PROJECT_FILE_NAME).write_text(yaml.dump({"environments": {}}))
        link = resolve_project_link(start_dir=tmp_path)
        assert link is None

    def test_resolve_multi_env_no_default_no_alias(self, tmp_path: pathlib.Path) -> None:
        (tmp_path / PROJECT_FILE_NAME).write_text(
            yaml.dump({"environments": {"dev": {"app": "myapi-dev"}}})
        )
        link = resolve_project_link(start_dir=tmp_path)
        assert link is None

    def test_write_upgrade_simple_to_multi(self, tmp_path: pathlib.Path) -> None:
        write_project_file("myapi", directory=tmp_path)
        write_project_file("myapi-dev", alias="dev", directory=tmp_path)
        data = yaml.safe_load((tmp_path / PROJECT_FILE_NAME).read_text())
        assert "environments" in data
        assert data["environments"]["dev"]["app"] == "myapi-dev"

    def test_remove_default_alias_updates_default(self, tmp_path: pathlib.Path) -> None:
        write_project_file("myapi-dev", alias="dev", directory=tmp_path)
        write_project_file("myapi-prod", alias="prod", directory=tmp_path)
        # dev is the default
        remove_alias("dev", directory=tmp_path)
        data = yaml.safe_load((tmp_path / PROJECT_FILE_NAME).read_text())
        assert data["default"] == "prod"


class TestLinkCLI:
    """CLI-level tests for apps:link:add / apps:link:remove commands."""

    def test_link_simple(self, cli_runner: CliRunner) -> None:
        with cli_runner.isolated_filesystem() as td:
            result = cli_runner.invoke(cli, ["apps", "link", "add", "myapi"])
            assert result.exit_code == 0
            assert "Linked to myapi" in result.output
            data = yaml.safe_load(pathlib.Path(td, PROJECT_FILE_NAME).read_text())
            assert data["app"] == "myapi"

    def test_link_with_alias(self, cli_runner: CliRunner) -> None:
        with cli_runner.isolated_filesystem() as td:
            result = cli_runner.invoke(
                cli, ["apps", "link", "add", "myapi-staging", "--as", "staging"]
            )
            assert result.exit_code == 0
            assert "staging" in result.output
            data = yaml.safe_load(pathlib.Path(td, PROJECT_FILE_NAME).read_text())
            assert data["environments"]["staging"]["app"] == "myapi-staging"

    def test_link_with_cluster(self, cli_runner: CliRunner) -> None:
        with cli_runner.isolated_filesystem() as td:
            result = cli_runner.invoke(
                cli,
                ["apps", "link", "add", "myapi-prod", "--as", "prod", "--cluster", "production"],
            )
            assert result.exit_code == 0
            data = yaml.safe_load(pathlib.Path(td, PROJECT_FILE_NAME).read_text())
            assert data["environments"]["prod"]["cluster"] == "production"

    def test_unlink_all(self, cli_runner: CliRunner) -> None:
        with cli_runner.isolated_filesystem() as td:
            cli_runner.invoke(cli, ["apps", "link", "add", "myapi"])
            result = cli_runner.invoke(cli, ["apps", "link", "remove"])
            assert result.exit_code == 0
            assert "Unlinked" in result.output
            assert not pathlib.Path(td, PROJECT_FILE_NAME).exists()

    def test_unlink_alias(self, cli_runner: CliRunner) -> None:
        with cli_runner.isolated_filesystem():
            cli_runner.invoke(cli, ["apps", "link", "add", "myapi-dev", "--as", "dev"])
            cli_runner.invoke(cli, ["apps", "link", "add", "myapi-prod", "--as", "prod"])
            result = cli_runner.invoke(cli, ["apps", "link", "remove", "--as", "dev"])
            assert result.exit_code == 0
            assert "Removed alias" in result.output

    def test_unlink_no_file(self, cli_runner: CliRunner) -> None:
        with cli_runner.isolated_filesystem():
            result = cli_runner.invoke(cli, ["apps", "link", "remove"])
            assert result.exit_code == 0
            assert "No .kuberoku file" in result.output

    def test_unlink_alias_not_found(self, cli_runner: CliRunner) -> None:
        with cli_runner.isolated_filesystem():
            cli_runner.invoke(cli, ["apps", "link", "add", "myapi-dev", "--as", "dev"])
            result = cli_runner.invoke(cli, ["apps", "link", "remove", "--as", "nope"])
            assert result.exit_code == 0
            assert "not found" in result.output

    def test_link_help(self, cli_runner: CliRunner) -> None:
        result = cli_runner.invoke(cli, ["apps", "link", "add", "--help"])
        assert result.exit_code == 0
        assert "Link current directory" in result.output

    # ── Colon syntax tests ──────────────────────────────────────

    def test_colon_syntax_link_add(self, cli_runner: CliRunner) -> None:
        with cli_runner.isolated_filesystem() as td:
            result = cli_runner.invoke(cli, ["apps:link:add", "myapi"])
            assert result.exit_code == 0
            assert "Linked to myapi" in result.output
            data = yaml.safe_load(pathlib.Path(td, PROJECT_FILE_NAME).read_text())
            assert data["app"] == "myapi"

    def test_colon_syntax_link_remove(self, cli_runner: CliRunner) -> None:
        with cli_runner.isolated_filesystem() as td:
            cli_runner.invoke(cli, ["apps:link:add", "myapi"])
            result = cli_runner.invoke(cli, ["apps:link:remove"])
            assert result.exit_code == 0
            assert "Unlinked" in result.output
            assert not pathlib.Path(td, PROJECT_FILE_NAME).exists()

    # ── Default (no subcommand) test ────────────────────────────

    def test_link_no_subcommand_shows_info(self, cli_runner: CliRunner) -> None:
        with cli_runner.isolated_filesystem():
            cli_runner.invoke(cli, ["apps", "link", "add", "myapi"])
            result = cli_runner.invoke(cli, ["apps", "link"])
            assert result.exit_code == 0
            assert "Linked to myapi" in result.output

    def test_link_no_subcommand_no_link(self, cli_runner: CliRunner) -> None:
        with cli_runner.isolated_filesystem():
            result = cli_runner.invoke(cli, ["apps", "link"])
            assert result.exit_code == 0
            assert "No project link found" in result.output
