"""Tests for vr render command."""

import json
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.commands.render import (
    parse_set_values,
    validate_rendered_docs,
)
from vr_cli.commands.validate import ValidationResult


runner = CliRunner()


class TestParseSetValues:
    """Tests for parse_set_values function."""

    def test_parse_single_value(self):
        """Should parse single key=value argument."""
        result = parse_set_values(["key=value"])
        assert result == {"key": "value"}

    def test_parse_multiple_values(self):
        """Should parse multiple key=value arguments."""
        result = parse_set_values(["key1=value1", "key2=value2"])
        assert result == {"key1": "value1", "key2": "value2"}

    def test_parse_value_with_equals(self):
        """Should handle values containing equals signs."""
        result = parse_set_values(["key=value=with=equals"])
        assert result == {"key": "value=with=equals"}

    def test_parse_strips_whitespace(self):
        """Should strip whitespace from keys and values."""
        result = parse_set_values(["  key  =  value  "])
        assert result == {"key": "value"}

    def test_parse_invalid_format_raises(self):
        """Should raise BadParameter for invalid format."""
        import typer
        with pytest.raises(typer.BadParameter):
            parse_set_values(["invalid-no-equals"])

    def test_parse_empty_value(self):
        """Should handle empty values."""
        result = parse_set_values(["key="])
        assert result == {"key": ""}


class TestValidateRenderedDocs:
    """Tests for validate_rendered_docs function."""

    def test_valid_deployment(self):
        """Should pass for valid Deployment document."""
        docs = [{
            "kind": "Deployment",
            "metadata": {"name": "test"},
            "spec": {"env": {"KEY": "value"}}
        }]
        result = ValidationResult()
        validate_rendered_docs(docs, result)
        assert result.is_valid

    def test_missing_kind(self):
        """Should fail when kind is missing."""
        docs = [{"metadata": {"name": "test"}}]
        result = ValidationResult()
        validate_rendered_docs(docs, result)
        assert not result.is_valid

    def test_unknown_kind(self):
        """Should fail for unknown kind."""
        docs = [{
            "kind": "UnknownKind",
            "metadata": {"name": "test"}
        }]
        result = ValidationResult()
        validate_rendered_docs(docs, result)
        assert not result.is_valid

    def test_invalid_spec_keys(self):
        """Should fail for invalid spec keys."""
        docs = [{
            "kind": "Deployment",
            "metadata": {"name": "test"},
            "spec": {"invalidKey": "value"}
        }]
        result = ValidationResult()
        validate_rendered_docs(docs, result)
        assert not result.is_valid

    def test_all_valid_spec_keys(self):
        """Should pass for all valid spec keys."""
        docs = [{
            "kind": "Deployment",
            "metadata": {"name": "test"},
            "spec": {
                "env": {"KEY": "value"},
                "stt": {"model": "nova-3"},
                "recording": {"enabled": True},
                "errorFallback": {"type": "webhook"},
                "startupAudio": {"url": "https://example.com/audio.mp3"},
                "webhook": {"url": "https://example.com/hook"},
                "region": "us-east-1",
                "tracing": True
            }
        }]
        result = ValidationResult()
        validate_rendered_docs(docs, result)
        assert result.is_valid

    def test_multiple_documents(self):
        """Should validate multiple documents."""
        docs = [
            {"kind": "Deployment", "metadata": {"name": "test1"}, "spec": {}},
            {"kind": "Deployment", "metadata": {"name": "test2"}, "spec": {}}
        ]
        result = ValidationResult()
        validate_rendered_docs(docs, result)
        assert result.is_valid


class TestRenderCommand:
    """Tests for the render command."""

    def test_render_not_voicerun_project(self, temp_dir, monkeypatch):
        """Should fail when not in a voicerun project."""
        monkeypatch.chdir(temp_dir)

        with patch("vr_cli.commands.render.print_error"), \
             patch("vr_cli.commands.render.print_info"):
            result = runner.invoke(app, ["render"])

        assert result.exit_code != 0

    def test_render_helm_not_available(self, voicerun_project, monkeypatch):
        """Should fail when Helm is not available."""
        monkeypatch.chdir(voicerun_project)

        with patch("vr_cli.commands.render.require_helm") as mock_helm, \
             patch("vr_cli.commands.render.print_error"), \
             patch("vr_cli.commands.render.print_info"):
            mock_helm.return_value = False

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

        assert result.exit_code != 0

    def test_render_with_helm(self, voicerun_project, monkeypatch):
        """Should render templates with Helm."""
        monkeypatch.chdir(voicerun_project)

        with patch("vr_cli.commands.render.require_helm") as mock_helm, \
             patch("vr_cli.commands.render.render_templates") as mock_render, \
             patch("vr_cli.commands.render.console"):
            mock_helm.return_value = True
            mock_render.return_value = [{
                "kind": "Deployment",
                "metadata": {"name": "test"},
                "spec": {"env": {"KEY": "value"}}
            }]

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

        assert result.exit_code == 0
        mock_render.assert_called_once()

    def test_render_with_values_file(self, voicerun_project, monkeypatch):
        """Should use specified values file."""
        monkeypatch.chdir(voicerun_project)
        # Use the values.yaml file that the fixture creates
        values_file = voicerun_project / ".voicerun" / "values.yaml"

        with patch("vr_cli.commands.render.require_helm") as mock_helm, \
             patch("vr_cli.commands.render.render_templates") as mock_render, \
             patch("vr_cli.commands.render.console"):
            mock_helm.return_value = True
            mock_render.return_value = [{
                "kind": "Deployment",
                "metadata": {"name": "test"},
                "spec": {}
            }]

            result = runner.invoke(app, ["render", "-f", str(values_file)])

        mock_render.assert_called_once()
        call_kwargs = mock_render.call_args[1]
        assert call_kwargs["values_file"] is not None

    def test_render_with_set_values(self, voicerun_project, monkeypatch):
        """Should pass set values to render."""
        monkeypatch.chdir(voicerun_project)

        with patch("vr_cli.commands.render.require_helm") as mock_helm, \
             patch("vr_cli.commands.render.render_templates") as mock_render, \
             patch("vr_cli.commands.render.console"):
            mock_helm.return_value = True
            mock_render.return_value = [{
                "kind": "Deployment",
                "metadata": {"name": "test"},
                "spec": {}
            }]

            result = runner.invoke(app, ["render", "--set", "key=value"])

        mock_render.assert_called_once()
        call_kwargs = mock_render.call_args[1]
        assert call_kwargs["set_values"] == {"key": "value"}

    def test_render_multiple_set_values(self, voicerun_project, monkeypatch):
        """Should handle multiple --set values."""
        monkeypatch.chdir(voicerun_project)

        with patch("vr_cli.commands.render.require_helm") as mock_helm, \
             patch("vr_cli.commands.render.render_templates") as mock_render, \
             patch("vr_cli.commands.render.console"):
            mock_helm.return_value = True
            mock_render.return_value = [{
                "kind": "Deployment",
                "metadata": {"name": "test"},
                "spec": {}
            }]

            result = runner.invoke(app, ["render", "-s", "key1=val1", "-s", "key2=val2"])

        call_kwargs = mock_render.call_args[1]
        assert call_kwargs["set_values"] == {"key1": "val1", "key2": "val2"}

    def test_render_output_yaml(self, voicerun_project, monkeypatch):
        """Should output YAML by default."""
        monkeypatch.chdir(voicerun_project)

        with patch("vr_cli.commands.render.require_helm") as mock_helm, \
             patch("vr_cli.commands.render.render_templates") as mock_render, \
             patch("vr_cli.commands.render.console") as mock_console:
            mock_helm.return_value = True
            mock_render.return_value = [{
                "kind": "Deployment",
                "metadata": {"name": "test"},
                "spec": {}
            }]

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

        # Console.print should have been called with YAML output
        mock_console.print.assert_called()

    def test_render_output_json(self, voicerun_project, monkeypatch):
        """Should output JSON with -o json."""
        monkeypatch.chdir(voicerun_project)

        with patch("vr_cli.commands.render.require_helm") as mock_helm, \
             patch("vr_cli.commands.render.render_templates") as mock_render, \
             patch("vr_cli.commands.render.console") as mock_console:
            mock_helm.return_value = True
            mock_render.return_value = [{
                "kind": "Deployment",
                "metadata": {"name": "test"},
                "spec": {}
            }]

            result = runner.invoke(app, ["render", "-o", "json"])

        # Should have output JSON format
        mock_console.print.assert_called()

    def test_render_invalid_output_format(self, voicerun_project, monkeypatch):
        """Should fail for invalid output format."""
        monkeypatch.chdir(voicerun_project)

        with patch("vr_cli.commands.render.require_helm") as mock_helm, \
             patch("vr_cli.commands.render.print_error"), \
             patch("vr_cli.commands.render.print_info"):
            mock_helm.return_value = True

            result = runner.invoke(app, ["render", "-o", "xml"])

        assert result.exit_code != 0

    def test_render_quiet_mode(self, voicerun_project, monkeypatch):
        """Should suppress validation messages in quiet mode."""
        monkeypatch.chdir(voicerun_project)

        with patch("vr_cli.commands.render.require_helm") as mock_helm, \
             patch("vr_cli.commands.render.render_templates") as mock_render, \
             patch("vr_cli.commands.render.console") as mock_console:
            mock_helm.return_value = True
            mock_render.return_value = [{
                "kind": "Deployment",
                "metadata": {"name": "test"},
                "spec": {}
            }]

            result = runner.invoke(app, ["render", "--quiet"])

        # Less output in quiet mode

    def test_render_no_templates(self, voicerun_project, monkeypatch):
        """Should handle case when no templates exist."""
        monkeypatch.chdir(voicerun_project)

        with patch("vr_cli.commands.render.require_helm") as mock_helm, \
             patch("vr_cli.commands.render.render_templates") as mock_render, \
             patch("vr_cli.commands.render.print_info"):
            mock_helm.return_value = True
            mock_render.return_value = []

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

        # Should exit gracefully

    def test_render_helm_error(self, voicerun_project, monkeypatch):
        """Should handle Helm rendering errors."""
        monkeypatch.chdir(voicerun_project)

        from vr_cli.utils.helm import HelmError

        with patch("vr_cli.commands.render.require_helm") as mock_helm, \
             patch("vr_cli.commands.render.render_templates") as mock_render, \
             patch("vr_cli.commands.render.print_error"):
            mock_helm.return_value = True
            mock_render.side_effect = HelmError("Template error")

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

        assert result.exit_code != 0


class TestRenderCommandEdgeCases:
    """Edge case tests for render command."""

    def test_render_nested_set_values(self, voicerun_project, monkeypatch):
        """Should handle nested set values like stt.model."""
        monkeypatch.chdir(voicerun_project)

        with patch("vr_cli.commands.render.require_helm") as mock_helm, \
             patch("vr_cli.commands.render.render_templates") as mock_render, \
             patch("vr_cli.commands.render.console"):
            mock_helm.return_value = True
            mock_render.return_value = [{
                "kind": "Deployment",
                "metadata": {"name": "test"},
                "spec": {}
            }]

            result = runner.invoke(app, ["render", "--set", "stt.model=nova-3"])

        call_kwargs = mock_render.call_args[1]
        assert call_kwargs["set_values"] == {"stt.model": "nova-3"}

    def test_render_invalid_set_format(self, voicerun_project, monkeypatch):
        """Should fail for invalid --set format."""
        monkeypatch.chdir(voicerun_project)

        with patch("vr_cli.commands.render.require_helm") as mock_helm, \
             patch("vr_cli.commands.render.print_error"):
            mock_helm.return_value = True

            result = runner.invoke(app, ["render", "--set", "invalid"])

        assert result.exit_code != 0

    def test_render_with_validation_errors(self, voicerun_project, monkeypatch):
        """Should exit with error when validation fails."""
        monkeypatch.chdir(voicerun_project)

        with patch("vr_cli.commands.render.require_helm") as mock_helm, \
             patch("vr_cli.commands.render.render_templates") as mock_render, \
             patch("vr_cli.commands.render.console"):
            mock_helm.return_value = True
            mock_render.return_value = [{
                "kind": "Deployment",
                "metadata": {"name": "test"},
                "spec": {"invalidKey": "value"}  # Invalid spec key
            }]

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

        assert result.exit_code != 0
