"""Tests for the mld init command."""

import argparse
from pathlib import Path
from unittest.mock import patch

import pytest

from mld_sdk.init_command import (
    PluginNames,
    cmd_init,
    derive_names,
    render,
    write_files,
    _build_template_context,
    _get_sdk_version,
)


class TestDeriveNames:

    def test_simple_name(self):
        names = derive_names("Drug Response Analyzer")
        assert names.slug == "drug-response-analyzer"
        assert names.package_name == "mld-plugin-drug-response-analyzer"
        assert names.module_name == "mld_plugin_drug_response_analyzer"
        assert names.class_name == "DrugResponseAnalyzerPlugin"
        assert names.routes_prefix == "/drug-response-analyzer"

    def test_strips_existing_mld_plugin_prefix(self):
        names = derive_names("mld-plugin-my-tool")
        assert names.slug == "my-tool"
        assert names.package_name == "mld-plugin-my-tool"
        assert names.module_name == "mld_plugin_my_tool"
        assert names.class_name == "MyToolPlugin"

    def test_strips_mld_plugin_prefix_case_insensitive(self):
        names = derive_names("MLD-Plugin-Test")
        assert names.slug == "test"
        assert names.package_name == "mld-plugin-test"

    def test_strips_underscore_prefix(self):
        names = derive_names("mld_plugin_something")
        assert names.slug == "something"
        assert names.package_name == "mld-plugin-something"

    def test_single_word(self):
        names = derive_names("Metabolomics")
        assert names.slug == "metabolomics"
        assert names.package_name == "mld-plugin-metabolomics"
        assert names.module_name == "mld_plugin_metabolomics"
        assert names.class_name == "MetabolomicsPlugin"
        assert names.routes_prefix == "/metabolomics"

    def test_preserves_human_name(self):
        names = derive_names("  My Cool Plugin  ")
        assert names.human == "My Cool Plugin"

    def test_special_characters_become_hyphens(self):
        names = derive_names("MS Data Upload & Analyzer (v2)")
        assert names.slug == "ms-data-upload-analyzer-v2"

    def test_hyphenated_input(self):
        names = derive_names("dose-response")
        assert names.slug == "dose-response"
        assert names.class_name == "DoseResponsePlugin"

    def test_rejects_empty_name(self):
        with pytest.raises(ValueError, match="cannot be empty"):
            derive_names("   ")

    def test_rejects_name_without_alphanumeric(self):
        with pytest.raises(ValueError, match="alphanumeric"):
            derive_names("!!!")

    def test_rejects_name_starting_with_digit(self):
        with pytest.raises(ValueError, match="start with a letter"):
            derive_names("123 Analyzer")


class TestRender:

    def test_replaces_placeholders(self):
        template = "Hello __NAME__, version __VERSION__"
        result = render(template, {"NAME": "World", "VERSION": "1.0"})
        assert result == "Hello World, version 1.0"

    def test_no_placeholders_unchanged(self):
        template = "No placeholders here"
        result = render(template, {"FOO": "bar"})
        assert result == "No placeholders here"

    def test_multiple_occurrences(self):
        template = "__X__ and __X__"
        result = render(template, {"X": "val"})
        assert result == "val and val"

    def test_case_insensitive_keys(self):
        """Context keys are uppercased internally."""
        template = "__MYKEY__"
        result = render(template, {"mykey": "value"})
        assert result == "value"


class TestWriteFiles:

    def test_creates_backend_files(self, tmp_path: Path):
        context = _build_template_context(
            derive_names("Test Plugin"),
            {"description": "A test", "type": "analysis"},
        )
        created = write_files(tmp_path, context, include_frontend=False)

        assert (tmp_path / "pyproject.toml").exists()
        assert (tmp_path / "src" / "mld_plugin_test_plugin" / "__init__.py").exists()
        assert (tmp_path / "src" / "mld_plugin_test_plugin" / "plugin.py").exists()
        assert (tmp_path / "src" / "mld_plugin_test_plugin" / "routers" / "analysis.py").exists()
        assert (tmp_path / "tests" / "test_plugin.py").exists()
        assert (tmp_path / "scripts" / "dev.sh").exists()
        assert (tmp_path / ".github" / "workflows" / "ci.yml").exists()
        assert "frontend/package.json" not in created

    def test_creates_frontend_files(self, tmp_path: Path):
        context = _build_template_context(
            derive_names("Test Plugin"),
            {"description": "A test", "type": "analysis"},
        )
        created = write_files(tmp_path, context, include_frontend=True)

        assert (tmp_path / "frontend" / "package.json").exists()
        assert (tmp_path / "frontend" / "vite.config.ts").exists()
        assert (tmp_path / "frontend" / "src" / "App.vue").exists()
        assert (tmp_path / "frontend" / "src" / "views" / "DashboardView.vue").exists()
        assert "frontend/package.json" in created

    def test_pyproject_contains_entry_point(self, tmp_path: Path):
        context = _build_template_context(
            derive_names("My Analyzer"),
            {"description": "Desc", "type": "analysis"},
        )
        write_files(tmp_path, context, include_frontend=False)

        content = (tmp_path / "pyproject.toml").read_text()
        assert "mld_plugin_my_analyzer.plugin:MyAnalyzerPlugin" in content
        assert 'name = "mld-plugin-my-analyzer"' in content

    def test_plugin_py_contains_class(self, tmp_path: Path):
        context = _build_template_context(
            derive_names("My Analyzer"),
            {"description": "Desc", "type": "analysis"},
        )
        write_files(tmp_path, context, include_frontend=False)

        content = (tmp_path / "src" / "mld_plugin_my_analyzer" / "plugin.py").read_text()
        assert "class MyAnalyzerPlugin(AnalysisPlugin):" in content
        assert "def create_standalone_app():" in content

    def test_experiment_design_type(self, tmp_path: Path):
        context = _build_template_context(
            derive_names("MS Designer"),
            {"description": "Desc", "type": "experiment-design"},
        )
        write_files(tmp_path, context, include_frontend=False)

        content = (tmp_path / "src" / "mld_plugin_ms_designer" / "plugin.py").read_text()
        assert "PluginType.EXPERIMENT_DESIGN" in content

    def test_vite_config_has_routes_prefix(self, tmp_path: Path):
        context = _build_template_context(
            derive_names("My Tool"),
            {"description": "Desc", "type": "analysis"},
        )
        write_files(tmp_path, context, include_frontend=True)

        content = (tmp_path / "frontend" / "vite.config.ts").read_text()
        assert "base: '/my-tool/'" in content


class TestCmdInit:

    def test_scaffolds_full_project(self, tmp_path: Path):
        target = tmp_path / "my-plugin"
        args = argparse.Namespace(
            directory=str(target),
            name="My Plugin",
            description="A test plugin",
            type="analysis",
            no_frontend=False,
            no_install=True,
            no_git=True,
            force=False,
        )

        # Skip interactive prompts since all args are provided
        with patch("mld_sdk.init_command.prompt_config") as mock_prompt:
            mock_prompt.return_value = {
                "directory": str(target),
                "name": "My Plugin",
                "description": "A test plugin",
                "type": "analysis",
                "include_frontend": "true",
            }
            cmd_init(args)

        assert (target / "pyproject.toml").exists()
        assert (target / "src" / "mld_plugin_my_plugin" / "plugin.py").exists()
        assert (target / "frontend" / "package.json").exists()

    def test_no_frontend_flag(self, tmp_path: Path):
        target = tmp_path / "backend-only"
        args = argparse.Namespace(
            directory=str(target),
            name="Backend Only",
            description="No frontend",
            type="analysis",
            no_frontend=True,
            no_install=True,
            no_git=True,
            force=False,
        )

        with patch("mld_sdk.init_command.prompt_config") as mock_prompt:
            mock_prompt.return_value = {
                "directory": str(target),
                "name": "Backend Only",
                "description": "No frontend",
                "type": "analysis",
                "include_frontend": "false",
            }
            cmd_init(args)

        assert (target / "pyproject.toml").exists()
        assert not (target / "frontend").exists()

    def test_rejects_non_empty_dir(self, tmp_path: Path):
        target = tmp_path / "existing"
        target.mkdir()
        (target / "some_file.txt").write_text("content")

        args = argparse.Namespace(
            directory=str(target),
            name="My Plugin",
            description="Desc",
            type="analysis",
            no_frontend=True,
            no_install=True,
            no_git=True,
            force=False,
        )

        with patch("mld_sdk.init_command.prompt_config") as mock_prompt:
            mock_prompt.return_value = {
                "directory": str(target),
                "name": "My Plugin",
                "description": "Desc",
                "type": "analysis",
                "include_frontend": "false",
            }
            with pytest.raises(SystemExit):
                cmd_init(args)

    def test_force_allows_non_empty_dir(self, tmp_path: Path):
        target = tmp_path / "existing"
        target.mkdir()
        (target / "some_file.txt").write_text("content")

        args = argparse.Namespace(
            directory=str(target),
            name="My Plugin",
            description="Desc",
            type="analysis",
            no_frontend=True,
            no_install=True,
            no_git=True,
            force=True,
        )

        with patch("mld_sdk.init_command.prompt_config") as mock_prompt:
            mock_prompt.return_value = {
                "directory": str(target),
                "name": "My Plugin",
                "description": "Desc",
                "type": "analysis",
                "include_frontend": "false",
            }
            cmd_init(args)

        assert (target / "pyproject.toml").exists()

    def test_rejects_invalid_plugin_name(self, tmp_path: Path):
        target = tmp_path / "invalid-name"
        args = argparse.Namespace(
            directory=str(target),
            name="123 Plugin",
            description="Desc",
            type="analysis",
            no_frontend=True,
            no_install=True,
            no_git=True,
            force=False,
        )

        with patch("mld_sdk.init_command.prompt_config") as mock_prompt:
            mock_prompt.return_value = {
                "directory": str(target),
                "name": "123 Plugin",
                "description": "Desc",
                "type": "analysis",
                "include_frontend": "false",
            }
            with pytest.raises(SystemExit):
                cmd_init(args)


class TestCLIIntegration:

    def test_init_subcommand_registered(self):
        """The init subcommand should be registered in the CLI parser."""
        from mld_sdk.cli import main

        # Should not crash — just show help and exit
        with pytest.raises(SystemExit):
            main(["init", "--help"])

    def test_init_via_main(self, tmp_path: Path):
        """Should be callable through the main CLI entry point."""
        from mld_sdk.cli import main

        target = tmp_path / "cli-test"

        with patch("mld_sdk.init_command.prompt_config") as mock_prompt:
            mock_prompt.return_value = {
                "directory": str(target),
                "name": "CLI Test",
                "description": "Test via CLI",
                "type": "analysis",
                "include_frontend": "false",
            }
            main([
                "init",
                str(target),
                "--name", "CLI Test",
                "--no-install",
                "--no-git",
                "--no-frontend",
            ])

        assert (target / "pyproject.toml").exists()


class TestGetSdkVersion:

    def test_returns_version_string(self):
        version = _get_sdk_version()
        # Should be a valid semver-like string
        assert version
        parts = version.split(".")
        assert len(parts) == 3
