"""Tests for Helm utility functions."""

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

from vr_cli.utils.helm import (
    is_helm_available,
    get_available_environments,
    load_values_file,
    load_agent_data,
    render_templates,
    render_templates_all_environments,
    HelmError,
)


class TestIsHelmAvailable:
    """Tests for is_helm_available function."""

    def test_helm_available(self):
        """Should return True when helm is in PATH."""
        with patch("shutil.which") as mock_which:
            mock_which.return_value = "/usr/local/bin/helm"
            assert is_helm_available() is True

    def test_helm_not_available(self):
        """Should return False when helm is not in PATH."""
        with patch("shutil.which") as mock_which:
            mock_which.return_value = None
            assert is_helm_available() is False


class TestGetAvailableEnvironments:
    """Tests for get_available_environments function."""

    def test_no_values_files(self, temp_dir):
        """Should return empty list when no values files exist."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()

        result = get_available_environments(voicerun_dir)
        assert result == []

    def test_single_environment(self, temp_dir):
        """Should return single environment."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        (voicerun_dir / "values.development.yaml").write_text("env: dev\n")

        result = get_available_environments(voicerun_dir)
        assert result == ["development"]

    def test_multiple_environments(self, temp_dir):
        """Should return all environments sorted."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        (voicerun_dir / "values.development.yaml").write_text("env: dev\n")
        (voicerun_dir / "values.production.yaml").write_text("env: prod\n")
        (voicerun_dir / "values.staging.yaml").write_text("env: staging\n")

        result = get_available_environments(voicerun_dir)
        assert result == ["development", "production", "staging"]

    def test_ignores_non_values_files(self, temp_dir):
        """Should ignore files that don't match values.*.yaml pattern."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        (voicerun_dir / "values.development.yaml").write_text("env: dev\n")
        (voicerun_dir / "agent.yaml").write_text("name: test\n")
        (voicerun_dir / "other.yaml").write_text("key: value\n")

        result = get_available_environments(voicerun_dir)
        assert result == ["development"]


class TestLoadValuesFile:
    """Tests for load_values_file function."""

    def test_load_existing_file(self, temp_dir):
        """Should load values from existing file."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        (voicerun_dir / "values.development.yaml").write_text(
            "environment: development\nsttModel: nova-3\n"
        )

        result = load_values_file(voicerun_dir, "development")
        assert result == {"environment": "development", "sttModel": "nova-3"}

    def test_load_nonexistent_file(self, temp_dir):
        """Should return None for nonexistent file."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()

        result = load_values_file(voicerun_dir, "nonexistent")
        assert result is None

    def test_load_empty_file(self, temp_dir):
        """Should return empty dict for empty file."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        (voicerun_dir / "values.empty.yaml").write_text("")

        result = load_values_file(voicerun_dir, "empty")
        assert result == {}


class TestLoadAgentData:
    """Tests for load_agent_data function."""

    def test_load_agent_data(self, temp_dir):
        """Should load agent name and description."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        (voicerun_dir / "agent.yaml").write_text(
            "name: my-agent\ndescription: My agent description\n"
        )

        result = load_agent_data(voicerun_dir)
        assert result == {
            "Name": "my-agent",
            "Description": "My agent description"
        }

    def test_load_agent_data_missing_file(self, temp_dir):
        """Should return empty dict when agent.yaml doesn't exist."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()

        result = load_agent_data(voicerun_dir)
        assert result == {}

    def test_load_agent_data_minimal(self, temp_dir):
        """Should handle minimal agent.yaml."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        (voicerun_dir / "agent.yaml").write_text("apiVersion: voicerun/v1\n")

        result = load_agent_data(voicerun_dir)
        assert result == {"Name": "", "Description": ""}


class TestRenderTemplates:
    """Tests for render_templates function."""

    def test_render_requires_helm(self, temp_dir):
        """Should raise HelmError when Helm is not available."""
        with patch("vr_cli.utils.helm.is_helm_available") as mock_helm:
            mock_helm.return_value = False

            with pytest.raises(HelmError) as exc_info:
                render_templates(temp_dir)

            assert "Helm CLI not found" in str(exc_info.value)

    def test_render_no_templates(self, temp_dir):
        """Should return empty list when no templates directory."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()

        with patch("vr_cli.utils.helm.is_helm_available") as mock_helm:
            mock_helm.return_value = True

            result = render_templates(temp_dir)

        assert result == []

    def test_render_with_environment(self, temp_dir):
        """Should use environment values file."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        templates_dir = voicerun_dir / "templates"
        templates_dir.mkdir()

        (voicerun_dir / "agent.yaml").write_text("name: test\n")
        (voicerun_dir / "values.development.yaml").write_text("environment: dev\n")
        (templates_dir / "deployment.yaml").write_text(
            "apiVersion: v1\nkind: Deployment\nmetadata:\n  name: test\n"
        )

        with patch("vr_cli.utils.helm.is_helm_available") as mock_helm, \
             patch("subprocess.run") as mock_run:
            mock_helm.return_value = True
            mock_run.return_value = Mock(
                stdout="apiVersion: v1\nkind: Deployment\nmetadata:\n  name: test\n",
                returncode=0
            )

            result = render_templates(temp_dir, environment="development")

        assert len(result) == 1

    def test_render_missing_environment(self, temp_dir):
        """Should raise HelmError for missing environment."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        templates_dir = voicerun_dir / "templates"
        templates_dir.mkdir()

        (templates_dir / "deployment.yaml").write_text("kind: Deployment\n")

        with patch("vr_cli.utils.helm.is_helm_available") as mock_helm:
            mock_helm.return_value = True

            with pytest.raises(HelmError) as exc_info:
                render_templates(temp_dir, environment="nonexistent")

            assert "Values file not found" in str(exc_info.value)

    def test_render_with_values_file(self, temp_dir):
        """Should use specified values file."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        templates_dir = voicerun_dir / "templates"
        templates_dir.mkdir()

        values_file = temp_dir / "custom-values.yaml"
        values_file.write_text("customKey: customValue\n")
        (voicerun_dir / "agent.yaml").write_text("name: test\n")
        (templates_dir / "deployment.yaml").write_text("kind: Deployment\n")

        with patch("vr_cli.utils.helm.is_helm_available") as mock_helm, \
             patch("subprocess.run") as mock_run:
            mock_helm.return_value = True
            mock_run.return_value = Mock(
                stdout="kind: Deployment\n",
                returncode=0
            )

            result = render_templates(temp_dir, values_file=values_file)

        assert len(result) >= 0

    def test_render_with_set_values(self, temp_dir):
        """Should apply set_values overrides."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        templates_dir = voicerun_dir / "templates"
        templates_dir.mkdir()

        (voicerun_dir / "agent.yaml").write_text("name: test\n")
        (templates_dir / "deployment.yaml").write_text("kind: Deployment\n")

        with patch("vr_cli.utils.helm.is_helm_available") as mock_helm, \
             patch("subprocess.run") as mock_run:
            mock_helm.return_value = True
            mock_run.return_value = Mock(
                stdout="kind: Deployment\n",
                returncode=0
            )

            result = render_templates(
                temp_dir,
                set_values={"key": "value", "nested.key": "nested_value"}
            )

        # Values should be passed to Helm

    def test_render_nested_set_values(self, temp_dir):
        """Should handle nested set_values like stt.model."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        templates_dir = voicerun_dir / "templates"
        templates_dir.mkdir()

        (voicerun_dir / "agent.yaml").write_text("name: test\n")
        (templates_dir / "deployment.yaml").write_text("kind: Deployment\n")

        with patch("vr_cli.utils.helm.is_helm_available") as mock_helm, \
             patch("subprocess.run") as mock_run:
            mock_helm.return_value = True
            mock_run.return_value = Mock(
                stdout="kind: Deployment\n",
                returncode=0
            )

            render_templates(
                temp_dir,
                set_values={"stt.model": "nova-3", "stt.language": "en"}
            )

        # Nested values should be properly structured

    def test_render_helm_failure(self, temp_dir):
        """Should raise HelmError on Helm command failure."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        templates_dir = voicerun_dir / "templates"
        templates_dir.mkdir()

        (voicerun_dir / "agent.yaml").write_text("name: test\n")
        (templates_dir / "deployment.yaml").write_text("kind: Deployment\n")

        with patch("vr_cli.utils.helm.is_helm_available") as mock_helm, \
             patch("subprocess.run") as mock_run:
            mock_helm.return_value = True
            mock_run.side_effect = subprocess.CalledProcessError(
                1, "helm", stderr="Template error"
            )

            with pytest.raises(HelmError):
                render_templates(temp_dir)

    def test_render_values_file_not_found(self, temp_dir):
        """Should raise HelmError when values file doesn't exist."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        templates_dir = voicerun_dir / "templates"
        templates_dir.mkdir()

        (templates_dir / "deployment.yaml").write_text("kind: Deployment\n")
        nonexistent_file = temp_dir / "nonexistent.yaml"

        with patch("vr_cli.utils.helm.is_helm_available") as mock_helm:
            mock_helm.return_value = True

            with pytest.raises(HelmError) as exc_info:
                render_templates(temp_dir, values_file=nonexistent_file)

            assert "not found" in str(exc_info.value)


class TestRenderTemplatesAllEnvironments:
    """Tests for render_templates_all_environments function."""

    def test_render_all_environments(self, temp_dir):
        """Should render templates for all available environments."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        templates_dir = voicerun_dir / "templates"
        templates_dir.mkdir()

        (voicerun_dir / "agent.yaml").write_text("name: test\n")
        (voicerun_dir / "values.development.yaml").write_text("env: dev\n")
        (voicerun_dir / "values.production.yaml").write_text("env: prod\n")
        (templates_dir / "deployment.yaml").write_text("kind: Deployment\n")

        with patch("vr_cli.utils.helm.is_helm_available") as mock_helm, \
             patch("subprocess.run") as mock_run:
            mock_helm.return_value = True
            mock_run.return_value = Mock(
                stdout="kind: Deployment\n",
                returncode=0
            )

            result = render_templates_all_environments(temp_dir)

        assert "development" in result
        assert "production" in result


class TestHelmTemplateTransformation:
    """Tests for Helm template syntax transformation."""

    def test_transforms_values_syntax(self, temp_dir):
        """Should transform {{ .Values.x }} to {{ .Values.Values.x }}."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        templates_dir = voicerun_dir / "templates"
        templates_dir.mkdir()

        (voicerun_dir / "agent.yaml").write_text("name: test\n")
        (templates_dir / "deployment.yaml").write_text(
            "env: {{ .Values.environment }}\n"
        )

        with patch("vr_cli.utils.helm.is_helm_available") as mock_helm, \
             patch("subprocess.run") as mock_run:
            mock_helm.return_value = True
            mock_run.return_value = Mock(stdout="env: dev\n", returncode=0)

            render_templates(temp_dir)

        # Verify subprocess was called with transformed chart

    def test_transforms_agent_syntax(self, temp_dir):
        """Should transform {{ .Agent.x }} to {{ .Values.Agent.x }}."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        templates_dir = voicerun_dir / "templates"
        templates_dir.mkdir()

        (voicerun_dir / "agent.yaml").write_text("name: test\n")
        (templates_dir / "deployment.yaml").write_text(
            "name: {{ .Agent.Name }}\n"
        )

        with patch("vr_cli.utils.helm.is_helm_available") as mock_helm, \
             patch("subprocess.run") as mock_run:
            mock_helm.return_value = True
            mock_run.return_value = Mock(stdout="name: test\n", returncode=0)

            render_templates(temp_dir)

        # Verify transformation was applied


class TestHelmEdgeCases:
    """Edge case tests for Helm utilities."""

    def test_handles_yml_extension(self, temp_dir):
        """Should handle .yml extension as well as .yaml."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        templates_dir = voicerun_dir / "templates"
        templates_dir.mkdir()

        (voicerun_dir / "agent.yaml").write_text("name: test\n")
        (templates_dir / "deployment.yml").write_text("kind: Deployment\n")

        with patch("vr_cli.utils.helm.is_helm_available") as mock_helm, \
             patch("subprocess.run") as mock_run:
            mock_helm.return_value = True
            mock_run.return_value = Mock(
                stdout="kind: Deployment\n",
                returncode=0
            )

            result = render_templates(temp_dir)

        # Should process .yml files

    def test_handles_multiple_documents(self, temp_dir):
        """Should parse multiple YAML documents in output."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        templates_dir = voicerun_dir / "templates"
        templates_dir.mkdir()

        (voicerun_dir / "agent.yaml").write_text("name: test\n")
        (templates_dir / "deployment.yaml").write_text("kind: Deployment\n")

        with patch("vr_cli.utils.helm.is_helm_available") as mock_helm, \
             patch("subprocess.run") as mock_run:
            mock_helm.return_value = True
            mock_run.return_value = Mock(
                stdout="kind: Deployment\nmetadata:\n  name: test1\n---\nkind: Deployment\nmetadata:\n  name: test2\n",
                returncode=0
            )

            result = render_templates(temp_dir)

        assert len(result) == 2

    def test_skips_empty_documents(self, temp_dir):
        """Should skip empty YAML documents."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        templates_dir = voicerun_dir / "templates"
        templates_dir.mkdir()

        (voicerun_dir / "agent.yaml").write_text("name: test\n")
        (templates_dir / "deployment.yaml").write_text("kind: Deployment\n")

        with patch("vr_cli.utils.helm.is_helm_available") as mock_helm, \
             patch("subprocess.run") as mock_run:
            mock_helm.return_value = True
            # Output with empty document between
            mock_run.return_value = Mock(
                stdout="kind: Deployment\n---\n---\nkind: Service\n",
                returncode=0
            )

            result = render_templates(temp_dir)

        # Should have 2 documents, not 3
        assert len(result) == 2
