"""Tests for vr validate 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.commands.validate import (
    ValidationResult,
    validate_handler,
    validate_voicerun_dir,
    validate_yaml_file,
    validate_agent_yaml,
    validate_templates,
    templates_contain_helm_syntax,
    strip_helm_syntax,
)


runner = CliRunner()


class TestValidationResult:
    """Tests for ValidationResult class."""

    def test_initial_state(self):
        """Should start with empty passed and failed lists."""
        result = ValidationResult()
        assert result.passed == []
        assert result.failed == []
        assert result.is_valid is True

    def test_add_pass(self):
        """Should add passed checks to list."""
        result = ValidationResult()
        result.add_pass("check1")
        result.add_pass("check2")
        assert result.passed == ["check1", "check2"]
        assert result.is_valid is True

    def test_add_fail(self):
        """Should add failed checks to list."""
        result = ValidationResult()
        result.add_fail("check1", "error1")
        assert result.failed == [("check1", "error1")]
        assert result.is_valid is False

    def test_is_valid_with_failures(self):
        """Should be invalid when there are failures."""
        result = ValidationResult()
        result.add_pass("check1")
        result.add_fail("check2", "error")
        assert result.is_valid is False


class TestValidateHandler:
    """Tests for validate_handler function."""

    def test_handler_not_found(self, temp_dir):
        """Should fail when handler.py doesn't exist."""
        result = ValidationResult()
        validate_handler(temp_dir, result)
        assert not result.is_valid
        assert any("handler.py not found" in error for _, error in result.failed)

    def test_handler_exists(self, temp_dir):
        """Should pass when handler.py exists with async def handler."""
        (temp_dir / "handler.py").write_text("async def handler(event):\n    pass\n")
        result = ValidationResult()
        validate_handler(temp_dir, result)
        assert result.is_valid

    def test_handler_not_async(self, temp_dir):
        """Should fail when handler function is not async."""
        (temp_dir / "handler.py").write_text("def handler(event):\n    pass\n")
        result = ValidationResult()
        validate_handler(temp_dir, result)
        assert not result.is_valid
        assert any("must be async" in error for _, error in result.failed)

    def test_handler_missing_function(self, temp_dir):
        """Should fail when handler function is missing."""
        (temp_dir / "handler.py").write_text("def other_function():\n    pass\n")
        result = ValidationResult()
        validate_handler(temp_dir, result)
        assert not result.is_valid
        assert any("must contain" in error for _, error in result.failed)

    def test_handler_syntax_error(self, temp_dir):
        """Should fail on Python syntax errors."""
        (temp_dir / "handler.py").write_text("def broken(\n")
        result = ValidationResult()
        validate_handler(temp_dir, result)
        assert not result.is_valid
        assert any("Syntax error" in error for _, error in result.failed)

    def test_handler_with_decorator(self, temp_dir):
        """Should pass when handler has decorators."""
        content = """
from primfunctions.events import Event
from primfunctions.context import Context

async def handler(event: Event, context: Context):
    pass
"""
        (temp_dir / "handler.py").write_text(content)
        result = ValidationResult()
        validate_handler(temp_dir, result)
        assert result.is_valid


class TestValidateVoicerunDir:
    """Tests for validate_voicerun_dir function."""

    def test_voicerun_dir_not_found(self, temp_dir):
        """Should fail when .voicerun/ doesn't exist."""
        result = ValidationResult()
        exists = validate_voicerun_dir(temp_dir, result)
        assert not exists
        assert not result.is_valid

    def test_voicerun_dir_exists(self, temp_dir):
        """Should pass when .voicerun/ exists."""
        (temp_dir / ".voicerun").mkdir()
        result = ValidationResult()
        exists = validate_voicerun_dir(temp_dir, result)
        assert exists
        assert result.is_valid


class TestValidateYamlFile:
    """Tests for validate_yaml_file function."""

    def test_valid_yaml(self, temp_dir):
        """Should pass for valid YAML."""
        yaml_file = temp_dir / "test.yaml"
        yaml_file.write_text("key: value\nlist:\n  - item1\n  - item2\n")
        result = ValidationResult()
        data = validate_yaml_file(yaml_file, result, "test")
        assert data == {"key": "value", "list": ["item1", "item2"]}
        assert result.is_valid

    def test_invalid_yaml(self, temp_dir):
        """Should fail for invalid YAML."""
        yaml_file = temp_dir / "test.yaml"
        yaml_file.write_text("key:\n  - item\n bad: indent")  # Improper indentation
        result = ValidationResult()
        data = validate_yaml_file(yaml_file, result, "test")
        assert data is None
        assert not result.is_valid

    def test_yaml_with_helm_stripped(self, temp_dir):
        """Should handle Helm syntax when strip_helm=True."""
        yaml_file = temp_dir / "test.yaml"
        yaml_file.write_text("name: {{ .Values.name }}\nversion: 1.0\n")
        result = ValidationResult()
        data = validate_yaml_file(yaml_file, result, "test", strip_helm=True)
        assert data is not None
        assert result.is_valid


class TestValidateAgentYaml:
    """Tests for validate_agent_yaml function."""

    def test_agent_yaml_not_found(self, temp_dir):
        """Should fail when agent.yaml doesn't exist."""
        (temp_dir / ".voicerun").mkdir()
        result = ValidationResult()
        data = validate_agent_yaml(temp_dir, result)
        assert data is None
        assert not result.is_valid

    def test_agent_yaml_missing_name(self, temp_dir):
        """Should fail when name field is missing."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        (voicerun_dir / "agent.yaml").write_text("apiVersion: voicerun/v1\ndescription: test\n")
        result = ValidationResult()
        data = validate_agent_yaml(temp_dir, result)
        assert data is None
        assert not result.is_valid

    def test_agent_yaml_valid(self, temp_dir):
        """Should pass for valid agent.yaml."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        (voicerun_dir / "agent.yaml").write_text("apiVersion: voicerun/v1\nname: test-agent\n")
        result = ValidationResult()
        data = validate_agent_yaml(temp_dir, result)
        assert data is not None
        assert data["name"] == "test-agent"
        assert result.is_valid


class TestTemplatesContainHelmSyntax:
    """Tests for templates_contain_helm_syntax function."""

    def test_no_templates_dir(self, temp_dir):
        """Should return False when templates dir doesn't exist."""
        assert templates_contain_helm_syntax(temp_dir / "templates") is False

    def test_templates_without_helm(self, temp_dir):
        """Should return False when no Helm syntax present."""
        templates_dir = temp_dir / "templates"
        templates_dir.mkdir()
        (templates_dir / "test.yaml").write_text("key: value\n")
        assert templates_contain_helm_syntax(templates_dir) is False

    def test_templates_with_helm(self, temp_dir):
        """Should return True when Helm syntax present."""
        templates_dir = temp_dir / "templates"
        templates_dir.mkdir()
        (templates_dir / "test.yaml").write_text("name: {{ .Values.name }}\n")
        assert templates_contain_helm_syntax(templates_dir) is True


class TestStripHelmSyntax:
    """Tests for strip_helm_syntax function."""

    def test_strips_simple_helm(self):
        """Should replace Helm expressions with placeholders."""
        content = "name: {{ .Values.name }}\n"
        result = strip_helm_syntax(content)
        assert "{{ .Values.name }}" not in result
        assert "HELM_PLACEHOLDER" in result

    def test_preserves_non_helm_lines(self):
        """Should preserve lines without Helm syntax."""
        content = "apiVersion: v1\nkind: Deployment\n"
        result = strip_helm_syntax(content)
        assert result == content

    def test_handles_multiple_helm_expressions(self):
        """Should handle multiple lines with Helm syntax."""
        content = "name: {{ .Values.name }}\nenv: {{ .Values.env }}\nversion: 1.0\n"
        result = strip_helm_syntax(content)
        assert result.count("HELM_PLACEHOLDER") == 2
        assert "version: 1.0" in result


class TestValidateTemplates:
    """Tests for validate_templates function."""

    def test_no_templates_directory(self, temp_dir):
        """Should pass when no templates directory exists."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        result = ValidationResult()
        validate_templates(temp_dir, result)
        assert result.is_valid

    def test_empty_templates_directory(self, temp_dir):
        """Should pass when templates directory is empty."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        (voicerun_dir / "templates").mkdir()
        result = ValidationResult()
        validate_templates(temp_dir, result)
        assert result.is_valid

    def test_valid_deployment_template(self, temp_dir):
        """Should pass for valid deployment template."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        templates_dir = voicerun_dir / "templates"
        templates_dir.mkdir()

        deployment = """apiVersion: voicerun/v1
kind: Deployment
metadata:
  name: test
spec:
  env:
    KEY: value
"""
        (templates_dir / "deployment.yaml").write_text(deployment)
        result = ValidationResult()
        validate_templates(temp_dir, result)
        assert result.is_valid

    def test_invalid_spec_keys(self, temp_dir):
        """Should fail when deployment has invalid spec keys."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        templates_dir = voicerun_dir / "templates"
        templates_dir.mkdir()

        deployment = """apiVersion: voicerun/v1
kind: Deployment
metadata:
  name: test
spec:
  invalidKey: value
"""
        (templates_dir / "deployment.yaml").write_text(deployment)
        result = ValidationResult()
        validate_templates(temp_dir, result)
        assert not result.is_valid

    def test_missing_kind(self, temp_dir):
        """Should fail when template is missing kind field."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        templates_dir = voicerun_dir / "templates"
        templates_dir.mkdir()

        deployment = """apiVersion: voicerun/v1
metadata:
  name: test
"""
        (templates_dir / "deployment.yaml").write_text(deployment)
        result = ValidationResult()
        validate_templates(temp_dir, result)
        assert not result.is_valid

    def test_unknown_kind(self, temp_dir):
        """Should fail when template has unknown kind."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        templates_dir = voicerun_dir / "templates"
        templates_dir.mkdir()

        deployment = """apiVersion: voicerun/v1
kind: UnknownKind
metadata:
  name: test
"""
        (templates_dir / "deployment.yaml").write_text(deployment)
        result = ValidationResult()
        validate_templates(temp_dir, result)
        assert not result.is_valid


class TestValidateCommand:
    """Tests for the validate command via CLI."""

    def test_validate_valid_project(self, voicerun_project, monkeypatch):
        """Should pass for valid project."""
        monkeypatch.chdir(voicerun_project)

        with patch("vr_cli.commands.validate.require_helm") as mock_helm, \
             patch("vr_cli.commands.validate.console"):
            mock_helm.return_value = False

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

        # May fail due to Helm templates without Helm, but structure should be valid
        # The result depends on whether Helm is mocked

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

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

        assert result.exit_code != 0

    def test_validate_quiet_mode(self, voicerun_project, monkeypatch):
        """Should only output errors in quiet mode."""
        monkeypatch.chdir(voicerun_project)

        with patch("vr_cli.commands.validate.require_helm") as mock_helm, \
             patch("vr_cli.commands.validate.console") as mock_console:
            mock_helm.return_value = False

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

        # In quiet mode, fewer console.print calls should be made

    def test_validate_with_environment(self, voicerun_project, monkeypatch):
        """Should validate with specific environment."""
        monkeypatch.chdir(voicerun_project)

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

            result = runner.invoke(app, ["validate", "-e", "development"])

        mock_render.assert_called_once()


class TestValidateCommandEdgeCases:
    """Edge case tests for validate command."""

    def test_validate_with_yml_extension(self, temp_dir, monkeypatch):
        """Should validate .yml files as well as .yaml."""
        monkeypatch.chdir(temp_dir)

        # Create minimal project
        (temp_dir / "handler.py").write_text("async def handler(event):\n    pass\n")
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        (voicerun_dir / "agent.yaml").write_text("name: test\n")
        templates_dir = voicerun_dir / "templates"
        templates_dir.mkdir()
        (templates_dir / "deployment.yml").write_text(
            "apiVersion: v1\nkind: Deployment\nmetadata:\n  name: test\nspec:\n  env:\n    KEY: value\n"
        )

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

    def test_validate_auto_selects_environment(self, voicerun_project, monkeypatch):
        """Should auto-select environment when Helm templates detected."""
        monkeypatch.chdir(voicerun_project)

        # Create a values file with environment name pattern for auto-detection
        voicerun_dir = voicerun_project / ".voicerun"
        (voicerun_dir / "values.development.yaml").write_text("environment: development\n")

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

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

        # Should have auto-selected an environment
        mock_render.assert_called()
