# Tests for the package sanity check functionality

import os
import json
import pytest
from unittest.mock import patch, mock_open, MagicMock
from pathlib import Path
import sys

# Add parent directory to path so we can import the script
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from check_package_sanity import run_namcap, build_package, check_packages

class TestPackageSanity:
    
    def test_run_namcap_pkgbuild(self):
        """Test running namcap on a PKGBUILD file"""
        with patch('subprocess.run') as mock_run:
            # Configure the mock subprocess run
            mock_process = MagicMock()
            mock_process.stdout = "Warning: PKGBUILD has some minor issues"
            mock_process.stderr = ""
            mock_process.returncode = 0
            mock_run.return_value = mock_process
            
            # Run namcap on PKGBUILD
            stdout, stderr, returncode = run_namcap("/path/to/package/PKGBUILD", is_pkgbuild=True)
            
            # Verify subprocess was called correctly
            mock_run.assert_called_once()
            args, kwargs = mock_run.call_args
            assert args[0] == ['namcap', 'PKGBUILD']
            assert kwargs['cwd'] == "/path/to/package"
            
            # Verify output
            assert stdout == "Warning: PKGBUILD has some minor issues"
            assert stderr == ""
            assert returncode == 0
    
    def test_run_namcap_package(self):
        """Test running namcap on a built package file"""
        with patch('subprocess.run') as mock_run:
            # Configure the mock subprocess run
            mock_process = MagicMock()
            mock_process.stdout = "Warning: package has some issues"
            mock_process.stderr = ""
            mock_process.returncode = 0
            mock_run.return_value = mock_process
            
            # Run namcap on package file
            stdout, stderr, returncode = run_namcap("/path/to/package.pkg.tar.zst", is_pkgbuild=False)
            
            # Verify subprocess was called correctly
            mock_run.assert_called_once()
            args, kwargs = mock_run.call_args
            assert args[0] == ['namcap', '/path/to/package.pkg.tar.zst']
            assert kwargs['cwd'] == "."
            
            # Verify output
            assert stdout == "Warning: package has some issues"
            assert stderr == ""
            assert returncode == 0
    
    def test_run_namcap_error(self):
        """Test handling errors when running namcap"""
        with patch('subprocess.run') as mock_run:
            # Make subprocess.run raise an exception
            mock_run.side_effect = Exception("Command failed")
            
            # Run namcap
            stdout, stderr, returncode = run_namcap("/path/to/PKGBUILD", is_pkgbuild=True)
            
            # Verify error handling
            assert stdout == ""
            assert "Command failed" in stderr
            assert returncode == 1
    
    def test_build_package_success(self):
        """Test successful package building"""
        with patch('subprocess.run') as mock_run, \
             patch('pathlib.Path.glob') as mock_glob:
            # Configure the mock subprocess run
            mock_process = MagicMock()
            mock_process.stdout = "Package built successfully"
            mock_process.stderr = ""
            mock_process.returncode = 0
            mock_run.return_value = mock_process
            
            # Configure the Path.glob to return a package file
            mock_glob.return_value = [Path("/path/to/package/package-1.0-1-x86_64.pkg.tar.zst")]
            
            # Build package
            package_path, error, returncode = build_package("/path/to/package")
            
            # Verify subprocess was called correctly
            mock_run.assert_called_once()
            args, kwargs = mock_run.call_args
            assert args[0] == ['makepkg', '-df', '--noconfirm']
            assert kwargs['cwd'] == "/path/to/package"
            
            # Verify output
            assert package_path == "/path/to/package/package-1.0-1-x86_64.pkg.tar.zst"
            assert error == ""
            assert returncode == 0
    
    def test_build_package_failure(self):
        """Test package build failure"""
        with patch('subprocess.run') as mock_run:
            # Configure the mock subprocess run to fail
            mock_process = MagicMock()
            mock_process.stdout = ""
            mock_process.stderr = "error: Failed to build package"
            mock_process.returncode = 1
            mock_run.return_value = mock_process
            
            # Build package
            package_path, error, returncode = build_package("/path/to/package")
            
            # Verify subprocess was called correctly
            mock_run.assert_called_once()
            
            # Verify output
            assert package_path is None
            assert "Failed to build package" in error
            assert returncode == 1
    
    def test_build_package_no_package_file(self):
        """Test when makepkg succeeds but no package file is found"""
        with patch('subprocess.run') as mock_run, \
             patch('pathlib.Path.glob') as mock_glob:
            # Configure the mock subprocess run
            mock_process = MagicMock()
            mock_process.stdout = "Package built successfully"
            mock_process.stderr = ""
            mock_process.returncode = 0
            mock_run.return_value = mock_process
            
            # Configure the Path.glob to return no package files
            mock_glob.return_value = []
            
            # Build package
            package_path, error, returncode = build_package("/path/to/package")
            
            # Verify output
            assert package_path is None
            assert "No package file was built" in error
            assert returncode == 1
    
    def test_check_packages_all_good(self):
        """Test checking packages when all checks pass"""
        # We need to mock several functions and methods
        with patch('check_package_sanity.run_namcap') as mock_run_namcap, \
             patch('check_package_sanity.build_package') as mock_build_package, \
             patch('os.path.exists') as mock_exists, \
             patch('json.dump') as mock_json_dump:
            
            # Configure mocks
            mock_exists.return_value = True
            mock_run_namcap.return_value = ("", "", 0)  # No issues with PKGBUILD or package
            mock_build_package.return_value = ("/path/to/package.pkg.tar.zst", "", 0)  # Build succeeds
            
            # Check packages
            issues = check_packages(["/path/to/test-package"])
            
            # Verify no issues were found
            assert len(issues) == 0
            # Verify json.dump was not called (no issues to write)
            mock_json_dump.assert_not_called()
    
    def test_check_packages_with_issues(self):
        """Test checking packages when issues are found"""
        # We need to mock several functions and methods
        with patch('check_package_sanity.run_namcap') as mock_run_namcap, \
             patch('check_package_sanity.build_package') as mock_build_package, \
             patch('os.path.exists') as mock_exists, \
             patch('builtins.open', mock_open()) as mock_file, \
             patch('json.dump') as mock_json_dump:
            
            # Configure mocks
            mock_exists.return_value = True
            
            # First call is for PKGBUILD check, second for package check
            mock_run_namcap.side_effect = [
                ("Warning: PKGBUILD has dependency issues", "", 0),  # PKGBUILD issue
                ("Warning: package has architecture issues", "", 0)   # Package issue
            ]
            
            mock_build_package.return_value = ("/path/to/package.pkg.tar.zst", "", 0)  # Build succeeds
            
            # Check packages
            issues = check_packages(["/path/to/test-package"])
            
            # Verify issues were found and structured correctly
            assert len(issues) == 1
            assert issues[0]["package"] == "test-package"
            assert "dependency issues" in issues[0]["pkgbuild_issues"]
            assert "architecture issues" in issues[0]["package_issues"]
            assert issues[0]["has_issues"] == True
            
            # Verify issues were written to file
            mock_json_dump.assert_called_once()
    
    def test_check_packages_build_failure(self):
        """Test checking packages when build fails"""
        # We need to mock several functions and methods
        with patch('check_package_sanity.run_namcap') as mock_run_namcap, \
             patch('check_package_sanity.build_package') as mock_build_package, \
             patch('os.path.exists') as mock_exists, \
             patch('builtins.open', mock_open()) as mock_file, \
             patch('json.dump') as mock_json_dump:
            
            # Configure mocks
            mock_exists.return_value = True
            mock_run_namcap.return_value = ("", "", 0)  # No PKGBUILD issues
            mock_build_package.return_value = (None, "error: Failed to build package", 1)  # Build fails
            
            # Check packages
            issues = check_packages(["/path/to/test-package"])
            
            # Verify issues were found and structured correctly
            assert len(issues) == 1
            assert issues[0]["package"] == "test-package"
            assert "Failed to build package" in issues[0]["build_error"]
            assert issues[0]["has_issues"] == True
            
            # Verify issues were written to file
            mock_json_dump.assert_called_once()
    
    def test_check_packages_multiple(self):
        """Test checking multiple packages"""
        # We need to mock several functions and methods
        with patch('check_package_sanity.run_namcap') as mock_run_namcap, \
             patch('check_package_sanity.build_package') as mock_build_package, \
             patch('os.path.exists') as mock_exists, \
             patch('builtins.open', mock_open()) as mock_file, \
             patch('json.dump') as mock_json_dump:
            
            # Configure mocks
            mock_exists.return_value = True
            
            # First package has PKGBUILD issues but build and package are fine
            # Second package has no issues
            # Third package fails to build
            mock_run_namcap.side_effect = [
                ("Warning: PKGBUILD has issues", "", 0),  # First package PKGBUILD issue
                ("", "", 0),                               # First package has no package issues
                ("", "", 0),                               # Second package has no PKGBUILD issues
                ("", "", 0),                               # Second package has no package issues
                ("", "", 0)                                # Third package has no PKGBUILD issues
            ]
            
            mock_build_package.side_effect = [
                ("/path/to/package1.pkg.tar.zst", "", 0),  # First package builds fine
                ("/path/to/package2.pkg.tar.zst", "", 0),  # Second package builds fine
                (None, "error: Failed to build package", 1)  # Third package fails to build
            ]
            
            # Check packages
            issues = check_packages([
                "/path/to/package1", 
                "/path/to/package2",
                "/path/to/package3"
            ])
            
            # Verify issues were found and structured correctly
            assert len(issues) == 2  # Only package1 and package3 have issues
            assert issues[0]["package"] == "package1"
            assert "has issues" in issues[0]["pkgbuild_issues"]
            assert issues[1]["package"] == "package3"
            assert "Failed to build package" in issues[1]["build_error"]
            
            # Verify issues were written to file
            mock_json_dump.assert_called_once()

    def test_main_no_arguments(self):
        """Test main function with no arguments"""
        original_argv = sys.argv.copy()  # Save original argv
        sys.argv = ['check_package_sanity.py']  # Set to just the script name
        
        try:
            with patch('sys.exit') as mock_exit:
                # Import the module
                import check_package_sanity
                
                # Directly call main()
                check_package_sanity.main()
                
                # Since sys.exit is mocked, the function continues and checks an empty list
                # Verify that sys.exit was called with code 1 for the usage error
                mock_exit.assert_called_with(1)
        finally:
            sys.argv = original_argv  # Restore original argv

    @patch('sys.argv', ['check_package_sanity.py', '/path/to/package1', '/path/to/package2'])
    def test_main_with_arguments(self):
        """Test main function with package arguments"""
        with patch('check_package_sanity.check_packages') as mock_check_packages, \
             patch('sys.exit') as mock_exit:
            
            # Configure mock
            mock_check_packages.return_value = []  # No issues found
            
            # Import the module
            import check_package_sanity
            
            # Directly execute the main function
            check_package_sanity.main()
            
            # Verify check_packages was called with the right arguments
            mock_check_packages.assert_called_with(['/path/to/package1', '/path/to/package2'])
            
            # Verify sys.exit was called with success code 0
            mock_exit.assert_called_with(0) 