"""Tests for vr init command."""

import pytest
from pathlib import Path
from unittest.mock import patch, Mock

from typer.testing import CliRunner

from vr_cli.cli import app
from vr_cli.entities.agent_template import AgentTemplate
from vr_cli.commands.init import (
    get_template_path,
    render_template,
    get_dest_path,
    init,
)


runner = CliRunner()


class TestGetTemplatePath:
    """Tests for get_template_path function."""

    def test_returns_path_object(self):
        """Should return a Path object."""
        result = get_template_path()
        assert isinstance(result, Path)

    def test_path_ends_with_templates(self):
        """Should return path ending with 'templates'."""
        result = get_template_path()
        assert result.name == "templates"

    def test_templates_directory_exists(self):
        """Templates directory should exist."""
        result = get_template_path()
        assert result.exists()


class TestRenderTemplate:
    """Tests for render_template function."""

    def test_replaces_simple_variable(self):
        """Should replace {variable} with value."""
        content = "Hello {name}!"
        result = render_template(content, name="World")
        assert result == "Hello World!"

    def test_replaces_multiple_variables(self):
        """Should replace multiple variables."""
        content = "{greeting} {name}!"
        result = render_template(content, greeting="Hello", name="World")
        assert result == "Hello World!"

    def test_preserves_helm_syntax(self):
        """Should not replace {{ }} Helm template syntax."""
        content = "name: {{ .Values.name }}"
        result = render_template(content, name="test")
        assert result == "name: {{ .Values.name }}"

    def test_replaces_only_single_braces(self):
        """Should only replace {var} not {{var}}."""
        content = "{project_name} and {{ .Values.environment }}"
        result = render_template(content, project_name="myproject")
        assert result == "myproject and {{ .Values.environment }}"

    def test_handles_missing_variable(self):
        """Should leave unreplaced variables as-is."""
        content = "Hello {name}!"
        result = render_template(content)
        assert result == "Hello {name}!"


class TestGetDestPath:
    """Tests for get_dest_path function."""

    def test_gitignore_conversion(self):
        """Should convert 'gitignore' to '.gitignore'."""
        assert get_dest_path("gitignore") == ".gitignore"

    def test_voicerun_to_hidden(self):
        """Should convert 'voicerun/' to '.voicerun/'."""
        assert get_dest_path("voicerun/agent.yaml") == ".voicerun/agent.yaml"

    def test_preserves_other_paths(self):
        """Should preserve paths that don't need conversion."""
        assert get_dest_path("handler.py") == "handler.py"
        assert get_dest_path("README.md") == "README.md"

    def test_voicerun_nested_paths(self):
        """Should handle nested paths under voicerun/."""
        assert get_dest_path("voicerun/templates/deployment.yaml") == ".voicerun/templates/deployment.yaml"


class TestInitCommand:
    """Tests for the init command."""

    def test_init_creates_project_directory(self, temp_dir, monkeypatch):
        """Should create project directory with given name."""
        monkeypatch.chdir(temp_dir)

        with patch("vr_cli.commands.init.console"):
            result = runner.invoke(app, ["init", "my-project", "--yes"])

        project_dir = temp_dir / "my-project"
        assert project_dir.exists()
        assert project_dir.is_dir()

    def test_init_creates_handler_py(self, temp_dir, monkeypatch):
        """Should create handler.py file."""
        monkeypatch.chdir(temp_dir)

        with patch("vr_cli.commands.init.console"):
            runner.invoke(app, ["init", "my-project", "--yes"])

        handler_path = temp_dir / "my-project" / "handler.py"
        assert handler_path.exists()

    def test_init_creates_voicerun_directory(self, temp_dir, monkeypatch):
        """Should create .voicerun directory."""
        monkeypatch.chdir(temp_dir)

        with patch("vr_cli.commands.init.console"):
            runner.invoke(app, ["init", "my-project", "--yes"])

        voicerun_dir = temp_dir / "my-project" / ".voicerun"
        assert voicerun_dir.exists()
        assert voicerun_dir.is_dir()

    def test_init_creates_agent_yaml(self, temp_dir, monkeypatch):
        """Should create agent.yaml with project name."""
        monkeypatch.chdir(temp_dir)

        with patch("vr_cli.commands.init.console"):
            runner.invoke(app, ["init", "my-project", "--yes"])

        agent_yaml = temp_dir / "my-project" / ".voicerun" / "agent.yaml"
        assert agent_yaml.exists()

        content = agent_yaml.read_text()
        assert "my-project" in content

    def test_init_creates_agent_yaml_in_voicerun(self, temp_dir, monkeypatch):
        """Should create agent.yaml inside .voicerun directory."""
        monkeypatch.chdir(temp_dir)

        with patch("vr_cli.commands.init.console"):
            runner.invoke(app, ["init", "my-project", "--yes"])

        agent_yaml = temp_dir / "my-project" / ".voicerun" / "agent.yaml"
        assert agent_yaml.exists()

    def test_init_requires_name_with_yes_flag(self, temp_dir, monkeypatch):
        """Should fail when --yes is used without project name."""
        monkeypatch.chdir(temp_dir)

        with patch("vr_cli.commands.init.console"):
            result = runner.invoke(app, ["init", "--yes"])

        assert result.exit_code != 0

    def test_init_fails_on_existing_nonempty_directory(self, temp_dir, monkeypatch):
        """Should fail if directory exists and is not empty."""
        monkeypatch.chdir(temp_dir)

        # Create existing non-empty directory
        existing_dir = temp_dir / "my-project"
        existing_dir.mkdir()
        (existing_dir / "some_file.txt").write_text("content")

        with patch("vr_cli.commands.init.console"):
            result = runner.invoke(app, ["init", "my-project", "--yes"])

        assert result.exit_code != 0

    def test_init_force_overwrites_existing(self, temp_dir, monkeypatch):
        """Should overwrite existing files with --force flag."""
        monkeypatch.chdir(temp_dir)

        # Create existing non-empty directory
        existing_dir = temp_dir / "my-project"
        existing_dir.mkdir()
        (existing_dir / "some_file.txt").write_text("content")

        with patch("vr_cli.commands.init.console"):
            result = runner.invoke(app, ["init", "my-project", "--yes", "--force"])

        assert result.exit_code == 0
        assert (temp_dir / "my-project" / "handler.py").exists()

    def test_init_prompts_for_name_without_argument(self, temp_dir, monkeypatch):
        """Should prompt for project name when not provided."""
        monkeypatch.chdir(temp_dir)

        with patch("vr_cli.commands.init.prompt") as mock_prompt, \
             patch("vr_cli.commands.init.console"), \
             patch("typer.prompt") as mock_typer_prompt:
            mock_prompt.return_value = "prompted-project"
            mock_typer_prompt.return_value = ""  # Empty description

            result = runner.invoke(app, ["init"])

        mock_prompt.assert_called_once()

    def test_init_renders_project_name_in_templates(self, temp_dir, monkeypatch):
        """Should replace {project_name} in templates."""
        monkeypatch.chdir(temp_dir)

        with patch("vr_cli.commands.init.console"):
            runner.invoke(app, ["init", "custom-agent", "--yes"])

        agent_yaml = temp_dir / "custom-agent" / ".voicerun" / "agent.yaml"
        content = agent_yaml.read_text()
        assert "custom-agent" in content
        assert "{project_name}" not in content


class TestInitCommandInteractive:
    """Tests for interactive init command behavior."""

    def test_init_prompts_for_description(self, temp_dir, monkeypatch):
        """Should prompt for description in interactive mode."""
        monkeypatch.chdir(temp_dir)

        with patch("vr_cli.commands.init.prompt") as mock_prompt, \
             patch("vr_cli.commands.init.console"), \
             patch("typer.prompt") as mock_typer_prompt:
            mock_prompt.return_value = "test-project"
            mock_typer_prompt.return_value = "My description"

            runner.invoke(app, ["init"])

        mock_typer_prompt.assert_called()

    def test_init_uses_default_description_with_yes(self, temp_dir, monkeypatch):
        """Should use default description when --yes is used."""
        monkeypatch.chdir(temp_dir)

        with patch("vr_cli.commands.init.console"):
            runner.invoke(app, ["init", "my-project", "--yes"])

        agent_yaml = temp_dir / "my-project" / ".voicerun" / "agent.yaml"
        content = agent_yaml.read_text()
        # Default description includes project name
        assert "my-project" in content


class TestInitCommandEdgeCases:
    """Edge case tests for init command."""

    def test_init_handles_special_characters_in_name(self, temp_dir, monkeypatch):
        """Should handle project names with special characters."""
        monkeypatch.chdir(temp_dir)

        with patch("vr_cli.commands.init.console"):
            result = runner.invoke(app, ["init", "my-project-123", "--yes"])

        assert result.exit_code == 0
        assert (temp_dir / "my-project-123").exists()

    def test_init_creates_all_parent_directories(self, temp_dir, monkeypatch):
        """Should create all necessary parent directories."""
        monkeypatch.chdir(temp_dir)

        with patch("vr_cli.commands.init.console"):
            runner.invoke(app, ["init", "my-project", "--yes"])

        # Check .voicerun directory and its contents were created
        voicerun_dir = temp_dir / "my-project" / ".voicerun"
        assert voicerun_dir.exists()
        assert (voicerun_dir / "agent.yaml").exists()


# ============================================================================
# Init from Remote Template Tests
# ============================================================================


class TestInitFromTemplate:
    """Tests for vr init --template (remote template initialization)."""

    def _make_template(self, **overrides):
        defaults = dict(
            id="tmpl-123",
            name="my-template",
            description="A test template",
            variables=[],
            files={
                "handler.py": "async def handler(): pass",
                "utils.py": "def helper(): pass",
            },
        )
        defaults.update(overrides)
        return AgentTemplate(**defaults)

    def test_init_from_template_creates_files(self, temp_dir, monkeypatch):
        """Should create files from the remote template."""
        monkeypatch.chdir(temp_dir)
        template = self._make_template()

        with patch("vr_cli.commands.init.AgentTemplateRepository") as mock_repo_class, \
             patch("vr_cli.commands.init.console"):
            mock_repo = Mock()
            mock_repo.get_by_name_or_id.return_value = template
            mock_repo.get_by_id.return_value = template
            mock_repo_class.return_value = mock_repo

            result = runner.invoke(app, ["init", "--template", "my-template"])

        assert result.exit_code == 0
        project_dir = temp_dir / "my-template"
        assert (project_dir / "handler.py").exists()
        assert (project_dir / "utils.py").exists()
        assert (project_dir / "handler.py").read_text() == "async def handler(): pass"

    def test_init_from_template_custom_name(self, temp_dir, monkeypatch):
        """Should use provided name instead of template name."""
        monkeypatch.chdir(temp_dir)
        template = self._make_template()

        with patch("vr_cli.commands.init.AgentTemplateRepository") as mock_repo_class, \
             patch("vr_cli.commands.init.console"):
            mock_repo = Mock()
            mock_repo.get_by_name_or_id.return_value = template
            mock_repo.get_by_id.return_value = template
            mock_repo_class.return_value = mock_repo

            result = runner.invoke(app, ["init", "custom-name", "--template", "my-template"])

        assert result.exit_code == 0
        assert (temp_dir / "custom-name" / "handler.py").exists()
        assert not (temp_dir / "my-template").exists()

    def test_init_from_template_not_found(self, temp_dir, monkeypatch):
        """Should fail when template not found."""
        monkeypatch.chdir(temp_dir)

        with patch("vr_cli.commands.init.AgentTemplateRepository") as mock_repo_class, \
             patch("vr_cli.commands.init.console"):
            mock_repo = Mock()
            mock_repo.get_by_name_or_id.return_value = None
            mock_repo_class.return_value = mock_repo

            result = runner.invoke(app, ["init", "--template", "nonexistent"])

        assert result.exit_code != 0

    def test_init_from_template_with_variables(self, temp_dir, monkeypatch):
        """Should substitute --var values into template files."""
        monkeypatch.chdir(temp_dir)
        template = self._make_template(
            variables=[{"name": "greeting"}],
            files={"handler.py": "msg = '{greeting}'"},
        )

        with patch("vr_cli.commands.init.AgentTemplateRepository") as mock_repo_class, \
             patch("vr_cli.commands.init.console"):
            mock_repo = Mock()
            mock_repo.get_by_name_or_id.return_value = template
            mock_repo.get_by_id.return_value = template
            mock_repo_class.return_value = mock_repo

            result = runner.invoke(app, [
                "init", "--template", "my-template",
                "--var", "greeting=Hello World",
            ])

        assert result.exit_code == 0
        content = (temp_dir / "my-template" / "handler.py").read_text()
        assert "Hello World" in content
        assert "{greeting}" not in content

    def test_init_from_template_missing_var_prompts(self, temp_dir, monkeypatch):
        """Should prompt for missing required variables."""
        monkeypatch.chdir(temp_dir)
        template = self._make_template(
            variables=[{"name": "api_key"}],
            files={"handler.py": "key = '{api_key}'"},
        )

        with patch("vr_cli.commands.init.AgentTemplateRepository") as mock_repo_class, \
             patch("vr_cli.commands.init.console"), \
             patch("vr_cli.commands.init.prompt") as mock_prompt:
            mock_repo = Mock()
            mock_repo.get_by_name_or_id.return_value = template
            mock_repo.get_by_id.return_value = template
            mock_repo_class.return_value = mock_repo
            mock_prompt.return_value = "sk-test-123"

            result = runner.invoke(app, ["init", "--template", "my-template"])

        assert result.exit_code == 0
        mock_prompt.assert_called_once_with("api_key")

    def test_init_from_template_missing_var_yes_flag_fails(self, temp_dir, monkeypatch):
        """Should fail when --yes is used and a required variable is missing."""
        monkeypatch.chdir(temp_dir)
        template = self._make_template(
            variables=[{"name": "api_key"}],
            files={"handler.py": "key = '{api_key}'"},
        )

        with patch("vr_cli.commands.init.AgentTemplateRepository") as mock_repo_class, \
             patch("vr_cli.commands.init.console"):
            mock_repo = Mock()
            mock_repo.get_by_name_or_id.return_value = template
            mock_repo.get_by_id.return_value = template
            mock_repo_class.return_value = mock_repo

            result = runner.invoke(app, ["init", "--template", "my-template", "--yes"])

        assert result.exit_code != 0

    def test_init_from_template_creates_nested_dirs(self, temp_dir, monkeypatch):
        """Should create nested directory structure from template files."""
        monkeypatch.chdir(temp_dir)
        template = self._make_template(
            files={
                "handler.py": "async def handler(): pass",
                "lib/helpers.py": "def help(): pass",
                "lib/models/user.py": "class User: pass",
            },
        )

        with patch("vr_cli.commands.init.AgentTemplateRepository") as mock_repo_class, \
             patch("vr_cli.commands.init.console"):
            mock_repo = Mock()
            mock_repo.get_by_name_or_id.return_value = template
            mock_repo.get_by_id.return_value = template
            mock_repo_class.return_value = mock_repo

            result = runner.invoke(app, ["init", "--template", "my-template"])

        assert result.exit_code == 0
        project_dir = temp_dir / "my-template"
        assert (project_dir / "lib" / "helpers.py").exists()
        assert (project_dir / "lib" / "models" / "user.py").exists()

    def test_init_from_template_fails_on_existing_nonempty(self, temp_dir, monkeypatch):
        """Should fail if directory exists and is not empty."""
        monkeypatch.chdir(temp_dir)
        existing_dir = temp_dir / "my-template"
        existing_dir.mkdir()
        (existing_dir / "existing.txt").write_text("content")

        template = self._make_template()

        with patch("vr_cli.commands.init.AgentTemplateRepository") as mock_repo_class, \
             patch("vr_cli.commands.init.console"):
            mock_repo = Mock()
            mock_repo.get_by_name_or_id.return_value = template
            mock_repo.get_by_id.return_value = template
            mock_repo_class.return_value = mock_repo

            result = runner.invoke(app, ["init", "--template", "my-template"])

        assert result.exit_code != 0

    def test_init_from_template_force_overwrites(self, temp_dir, monkeypatch):
        """Should overwrite existing files with --force."""
        monkeypatch.chdir(temp_dir)
        existing_dir = temp_dir / "my-template"
        existing_dir.mkdir()
        (existing_dir / "existing.txt").write_text("content")

        template = self._make_template()

        with patch("vr_cli.commands.init.AgentTemplateRepository") as mock_repo_class, \
             patch("vr_cli.commands.init.console"):
            mock_repo = Mock()
            mock_repo.get_by_name_or_id.return_value = template
            mock_repo.get_by_id.return_value = template
            mock_repo_class.return_value = mock_repo

            result = runner.invoke(app, ["init", "--template", "my-template", "--force"])

        assert result.exit_code == 0
        assert (existing_dir / "handler.py").exists()

    def test_init_from_template_invalid_var_format(self, temp_dir, monkeypatch):
        """Should fail when --var is not in key=value format."""
        monkeypatch.chdir(temp_dir)
        template = self._make_template(variables=[{"name": "key"}])

        with patch("vr_cli.commands.init.AgentTemplateRepository") as mock_repo_class, \
             patch("vr_cli.commands.init.console"):
            mock_repo = Mock()
            mock_repo.get_by_name_or_id.return_value = template
            mock_repo.get_by_id.return_value = template
            mock_repo_class.return_value = mock_repo

            result = runner.invoke(app, ["init", "--template", "my-template", "--var", "badformat"])

        assert result.exit_code != 0
