"""Tests for the run module improvements."""
import subprocess
import tempfile
from pathlib import Path
from unittest.mock import patch, MagicMock, call

import pytest

from pytest_cream.run import run_tests


class TestRunTests:
    """Tests for run_tests function."""
    
    def test_run_tests_basic(self, tmp_path):
        """Test basic test execution."""
        output_file = tmp_path / "durations.log"
        tests_dir = tmp_path / "tests"
        tests_dir.mkdir()
        
        with patch('subprocess.run') as mock_run:
            mock_run.return_value = MagicMock(returncode=0)
            
            run_tests(
                output_file=str(output_file),
                tests_dir=str(tests_dir),
                python_executable="python3",
                use_uv=False  # Disable uv for this test
            )
            
            # Verify subprocess.run was called
            assert mock_run.called
            args = mock_run.call_args[0][0]
            assert args[0] == "python3"
            assert args[1] == "-m"
            assert args[2] == "pytest"
    
    def test_run_tests_handles_exit_code_0(self, tmp_path):
        """Test that exit code 0 (success) doesn't raise error."""
        output_file = tmp_path / "durations.log"
        tests_dir = tmp_path / "tests"
        tests_dir.mkdir()
        
        with patch('subprocess.run') as mock_run:
            mock_run.return_value = MagicMock(returncode=0)
            
            # Should not raise
            run_tests(
                output_file=str(output_file),
                tests_dir=str(tests_dir)
            )
    
    def test_run_tests_handles_exit_code_1(self, tmp_path):
        """Test that exit code 1 (deselected tests) doesn't raise error."""
        output_file = tmp_path / "durations.log"
        tests_dir = tmp_path / "tests"
        tests_dir.mkdir()
        
        with patch('subprocess.run') as mock_run:
            mock_run.return_value = MagicMock(returncode=1)
            
            # Should not raise - exit code 1 can mean deselected tests
            run_tests(
                output_file=str(output_file),
                tests_dir=str(tests_dir)
            )
    
    def test_run_tests_raises_on_exit_code_2_or_higher(self, tmp_path):
        """Test that exit code > 1 raises CalledProcessError."""
        output_file = tmp_path / "durations.log"
        tests_dir = tmp_path / "tests"
        tests_dir.mkdir()
        
        with patch('subprocess.run') as mock_run:
            mock_run.return_value = MagicMock(returncode=2)
            
            with pytest.raises(subprocess.CalledProcessError):
                run_tests(
                    output_file=str(output_file),
                    tests_dir=str(tests_dir)
                )
    
    def test_run_tests_uses_custom_python(self, tmp_path):
        """Test that custom Python executable is used."""
        output_file = tmp_path / "durations.log"
        tests_dir = tmp_path / "tests"
        tests_dir.mkdir()
        
        with patch('subprocess.run') as mock_run:
            mock_run.return_value = MagicMock(returncode=0)
            
            run_tests(
                output_file=str(output_file),
                tests_dir=str(tests_dir),
                python_executable="/custom/python3.11",
                use_uv=False
            )
            
            args = mock_run.call_args[0][0]
            assert args[0] == "/custom/python3.11"
    
    def test_run_tests_uses_sys_executable_by_default(self, tmp_path):
        """Test that sys.executable is used when no Python specified."""
        output_file = tmp_path / "durations.log"
        tests_dir = tmp_path / "tests"
        tests_dir.mkdir()
        
        with patch('subprocess.run') as mock_run:
            mock_run.return_value = MagicMock(returncode=0)
            with patch('sys.executable', '/default/python'):
                
                run_tests(
                    output_file=str(output_file),
                    tests_dir=str(tests_dir),
                    use_uv=False
                )
                
                args = mock_run.call_args[0][0]
                assert args[0] == '/default/python'
    
    def test_run_tests_writes_to_output_file(self, tmp_path):
        """Test that output is written to the specified file."""
        output_file = tmp_path / "durations.log"
        tests_dir = tmp_path / "tests"
        tests_dir.mkdir()
        
        with patch('subprocess.run') as mock_run:
            mock_run.return_value = MagicMock(returncode=0)
            with patch('builtins.open', MagicMock()) as mock_open:
                
                run_tests(
                    output_file=str(output_file),
                    tests_dir=str(tests_dir)
                )
                
                # Verify file was opened for writing
                mock_open.assert_called_once_with(str(output_file), "w")
                # Verify stdout was passed to subprocess
                assert 'stdout' in mock_run.call_args[1]
    
    def test_run_tests_includes_marker_filter(self, tmp_path):
        """Test that marker filter '-m not slow' is included in command."""
        output_file = tmp_path / "durations.log"
        tests_dir = tmp_path / "tests"
        tests_dir.mkdir()
        
        with patch('subprocess.run') as mock_run:
            mock_run.return_value = MagicMock(returncode=0)
            
            run_tests(
                output_file=str(output_file),
                tests_dir=str(tests_dir),
                use_uv=False
            )
            
            args = mock_run.call_args[0][0]
            # Find all occurrences of -m flag
            m_indices = [i for i, arg in enumerate(args) if arg == "-m"]
            # One should be for pytest module, one for marker
            assert len(m_indices) >= 2
            # Check that "not slow" marker is present
            assert "not slow" in args
    
    def test_run_tests_includes_pytest_options(self, tmp_path):
        """Test that pytest options are included in command."""
        output_file = tmp_path / "durations.log"
        tests_dir = tmp_path / "tests"
        tests_dir.mkdir()
        
        with patch('subprocess.run') as mock_run:
            mock_run.return_value = MagicMock(returncode=0)
            
            run_tests(
                output_file=str(output_file),
                tests_dir=str(tests_dir)
            )
            
            args = mock_run.call_args[0][0]
            # Check for expected pytest options
            assert "--durations=0" in args
            assert "--durations-min=0" in args
            assert "--tb=short" in args
            assert "--disable-warnings" in args
            assert "-q" in args


class TestExitCodeHandling:
    """Specific tests for exit code handling."""
    
    @pytest.mark.parametrize("exit_code", [0, 1])
    def test_acceptable_exit_codes(self, exit_code, tmp_path):
        """Test that exit codes 0 and 1 are acceptable."""
        output_file = tmp_path / "durations.log"
        tests_dir = tmp_path / "tests"
        tests_dir.mkdir()
        
        with patch('subprocess.run') as mock_run:
            mock_run.return_value = MagicMock(returncode=exit_code)
            
            # Should not raise
            try:
                run_tests(
                    output_file=str(output_file),
                    tests_dir=str(tests_dir)
                )
            except subprocess.CalledProcessError:
                pytest.fail(f"Exit code {exit_code} should not raise CalledProcessError")
    
    @pytest.mark.parametrize("exit_code", [2, 3, 4, 5])
    def test_error_exit_codes(self, exit_code, tmp_path):
        """Test that exit codes > 1 raise CalledProcessError."""
        output_file = tmp_path / "durations.log"
        tests_dir = tmp_path / "tests"
        tests_dir.mkdir()
        
        with patch('subprocess.run') as mock_run:
            mock_run.return_value = MagicMock(returncode=exit_code)
            
            with pytest.raises(subprocess.CalledProcessError) as exc_info:
                run_tests(
                    output_file=str(output_file),
                    tests_dir=str(tests_dir)
                )
            
            assert exc_info.value.returncode == exit_code


class TestPythonExecutableResolution:
    """Tests for Python executable resolution logic."""
    
    def test_python_version_number_conversion(self, tmp_path):
        """Test that version numbers are converted to python executable."""
        output_file = tmp_path / "durations.log"
        tests_dir = tmp_path / "tests"
        tests_dir.mkdir()
        
        with patch('subprocess.run') as mock_run:
            mock_run.return_value = MagicMock(returncode=0)
            
            run_tests(
                output_file=str(output_file),
                tests_dir=str(tests_dir),
                python_executable="3.11",
                use_uv=False
            )
            
            args = mock_run.call_args[0][0]
            assert args[0] == "python3.11"
    
    def test_python_with_python_prefix(self, tmp_path):
        """Test that executables starting with 'python' are used as-is."""
        output_file = tmp_path / "durations.log"
        tests_dir = tmp_path / "tests"
        tests_dir.mkdir()
        
        with patch('subprocess.run') as mock_run:
            mock_run.return_value = MagicMock(returncode=0)
            
            run_tests(
                output_file=str(output_file),
                tests_dir=str(tests_dir),
                python_executable="python3.11",
                use_uv=False
            )
            
            args = mock_run.call_args[0][0]
            assert args[0] == "python3.11"
    
    def test_absolute_path_python(self, tmp_path):
        """Test that absolute paths are used as-is."""
        output_file = tmp_path / "durations.log"
        tests_dir = tmp_path / "tests"
        tests_dir.mkdir()
        
        with patch('subprocess.run') as mock_run:
            mock_run.return_value = MagicMock(returncode=0)
            
            run_tests(
                output_file=str(output_file),
                tests_dir=str(tests_dir),
                python_executable="/usr/local/bin/python3",
                use_uv=False
            )
            
            args = mock_run.call_args[0][0]
            assert args[0] == "/usr/local/bin/python3"
