"""Tests for vr test command."""

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

from typer.testing import CliRunner

from vr_cli.cli import app
from vr_cli.commands.test import (
    load_agent_lock,
    run_pytest,
)


runner = CliRunner()


class TestLoadAgentLock:
    """Tests for load_agent_lock function."""

    def test_load_existing_lock(self, temp_dir):
        """Should load existing agent.lock file."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        lock_data = {"id": "agent-123", "functionId": "func-456"}
        (voicerun_dir / "agent.lock").write_text(json.dumps(lock_data))

        result = load_agent_lock(voicerun_dir)
        assert result == lock_data

    def test_load_nonexistent_lock(self, temp_dir):
        """Should return None when lock file doesn't exist."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()

        result = load_agent_lock(voicerun_dir)
        assert result is None

    def test_load_invalid_json(self, temp_dir):
        """Should return None for invalid JSON."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()
        (voicerun_dir / "agent.lock").write_text("invalid json")

        result = load_agent_lock(voicerun_dir)
        assert result is None


class TestRunPytest:
    """Tests for run_pytest function."""

    def test_run_pytest_default(self, temp_dir):
        """Should run pytest with default options."""
        # Create a minimal test file
        (temp_dir / "test_example.py").write_text("def test_pass(): assert True")

        with patch("subprocess.run") as mock_run:
            mock_run.return_value = Mock(returncode=0)

            result = run_pytest(temp_dir, None, verbose=False, coverage=False, extra_args=[])

        assert result == 0
        mock_run.assert_called_once()

    def test_run_pytest_verbose(self, temp_dir):
        """Should add -v flag when verbose is True."""
        with patch("subprocess.run") as mock_run:
            mock_run.return_value = Mock(returncode=0)

            run_pytest(temp_dir, None, verbose=True, coverage=False, extra_args=[])

        call_args = mock_run.call_args[0][0]
        assert "-v" in call_args

    def test_run_pytest_coverage(self, temp_dir):
        """Should add coverage flags when coverage is True."""
        with patch("subprocess.run") as mock_run:
            mock_run.return_value = Mock(returncode=0)

            run_pytest(temp_dir, None, verbose=False, coverage=True, extra_args=[])

        call_args = mock_run.call_args[0][0]
        assert "--cov=." in call_args
        assert "--cov-report=term-missing" in call_args

    def test_run_pytest_with_path(self, temp_dir):
        """Should use specified test path."""
        with patch("subprocess.run") as mock_run:
            mock_run.return_value = Mock(returncode=0)

            run_pytest(temp_dir, "tests/test_specific.py", verbose=False, coverage=False, extra_args=[])

        call_args = mock_run.call_args[0][0]
        assert "tests/test_specific.py" in call_args

    def test_run_pytest_with_extra_args(self, temp_dir):
        """Should pass extra arguments to pytest."""
        with patch("subprocess.run") as mock_run:
            mock_run.return_value = Mock(returncode=0)

            run_pytest(temp_dir, None, verbose=False, coverage=False, extra_args=["-k", "test_name", "-x"])

        call_args = mock_run.call_args[0][0]
        assert "-k" in call_args
        assert "test_name" in call_args
        assert "-x" in call_args

    def test_run_pytest_loads_variables_file(self, temp_dir):
        """Should load environment variables from .variables.env."""
        import os

        # Create variables file
        (temp_dir / ".variables.env").write_text("export TEST_VAR='test_value'\nexport OTHER_VAR='other'\n")

        # Clean up any existing values
        original_test_var = os.environ.pop("TEST_VAR", None)
        original_other_var = os.environ.pop("OTHER_VAR", None)

        try:
            with patch("subprocess.run") as mock_run:
                mock_run.return_value = Mock(returncode=0)

                run_pytest(temp_dir, None, verbose=False, coverage=False, extra_args=[])

            # Variables should be loaded into os.environ
            assert os.environ.get("TEST_VAR") == "test_value"
            assert os.environ.get("OTHER_VAR") == "other"
        finally:
            # Restore original values
            os.environ.pop("TEST_VAR", None)
            os.environ.pop("OTHER_VAR", None)
            if original_test_var:
                os.environ["TEST_VAR"] = original_test_var
            if original_other_var:
                os.environ["OTHER_VAR"] = original_other_var

    def test_run_pytest_uses_tests_directory(self, temp_dir):
        """Should use tests/ directory if it exists."""
        tests_dir = temp_dir / "tests"
        tests_dir.mkdir()

        with patch("subprocess.run") as mock_run:
            mock_run.return_value = Mock(returncode=0)

            run_pytest(temp_dir, None, verbose=False, coverage=False, extra_args=[])

        call_args = mock_run.call_args[0][0]
        assert str(tests_dir) in call_args


class TestTestCommand:
    """Tests for the test command."""

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

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

        assert result.exit_code != 0

    def test_test_validation_failure(self, temp_dir, monkeypatch):
        """Should fail when project validation fails."""
        monkeypatch.chdir(temp_dir)

        # Create minimal .voicerun directory but no handler.py
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()

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

        assert result.exit_code != 0

    def test_test_skip_install(self, voicerun_project, monkeypatch):
        """Should skip installation with --skip-install flag."""
        monkeypatch.chdir(voicerun_project)

        # Create a simple test file
        (voicerun_project / "test_example.py").write_text("def test_pass(): assert True")

        with patch("vr_cli.commands.test.console"), \
             patch("vr_cli.commands.test.print_info"), \
             patch("vr_cli.commands.test.print_success"), \
             patch("vr_cli.commands.test.setup_test_environment") as mock_setup, \
             patch("vr_cli.commands.test.run_pytest") as mock_pytest:
            mock_pytest.return_value = 0

            result = runner.invoke(app, ["test", "--skip-install"])

        # setup_test_environment should not have been called
        mock_setup.assert_not_called()

    def test_test_with_environment(self, voicerun_project_with_lock, monkeypatch):
        """Should fetch secrets when environment is specified."""
        monkeypatch.chdir(voicerun_project_with_lock)

        with patch("vr_cli.commands.test.console"), \
             patch("vr_cli.commands.test.print_info"), \
             patch("vr_cli.commands.test.print_success"), \
             patch("vr_cli.commands.test.print_warning"), \
             patch("vr_cli.commands.test.setup_test_environment") as mock_setup, \
             patch("vr_cli.commands.test.run_pytest") as mock_pytest:
            mock_setup.return_value = True
            mock_pytest.return_value = 0

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

        mock_setup.assert_called_once()
        # Verify environment was passed
        call_args = mock_setup.call_args
        assert call_args[0][2] == "development"  # environment argument

    def test_test_returns_pytest_exit_code(self, voicerun_project, monkeypatch):
        """Should return pytest's exit code."""
        monkeypatch.chdir(voicerun_project)

        with patch("vr_cli.commands.test.console"), \
             patch("vr_cli.commands.test.print_info"), \
             patch("vr_cli.commands.test.print_success"), \
             patch("vr_cli.commands.test.print_error"), \
             patch("vr_cli.commands.test.setup_test_environment") as mock_setup, \
             patch("vr_cli.commands.test.run_pytest") as mock_pytest:
            mock_setup.return_value = True
            mock_pytest.return_value = 1  # Test failure

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

        assert result.exit_code == 1

    def test_test_warns_about_missing_agent_lock(self, voicerun_project, monkeypatch):
        """Should warn when environment specified but no agent.lock."""
        monkeypatch.chdir(voicerun_project)

        with patch("vr_cli.commands.test.console"), \
             patch("vr_cli.commands.test.print_info"), \
             patch("vr_cli.commands.test.print_success"), \
             patch("vr_cli.commands.test.print_warning") as mock_warning, \
             patch("vr_cli.commands.test.setup_test_environment") as mock_setup, \
             patch("vr_cli.commands.test.run_pytest") as mock_pytest:
            mock_setup.return_value = True
            mock_pytest.return_value = 0

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

        mock_warning.assert_called()


