"""Additional tests for Core and Scanner modules to improve coverage.

MIT License - Copyright (c) 2024 DepCleaner
For feature requests or contributions, visit: https://github.com/Jermy-tech/depcleaner
"""
import tempfile
import json
from pathlib import Path
import pytest
from depcleaner import DepCleaner
from depcleaner.scanner import Scanner


# ============= Core Module Additional Tests =============

def test_core_filter_report():
    """Test filtering report by pattern."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        (tmppath / "test1.py").write_text("import os\n")
        (tmppath / "test2.py").write_text("import sys\n")
        
        cleaner = DepCleaner(tmppath)
        report = cleaner.scan()
        
        # Filter to only test1.py
        filtered = cleaner._filter_report(report, "*test1.py")
        
        assert len(filtered.all_imports) <= len(report.all_imports)


def test_core_find_duplicate_dependencies():
    """Test finding duplicate dependencies."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        (tmppath / "test.py").write_text("import os\n")
        
        req_file = tmppath / "requirements.txt"
        req_file.write_text("my-package==1.0\nmy_package==2.0\n")
        
        cleaner = DepCleaner(tmppath)
        duplicates = cleaner.find_duplicate_dependencies()
        
        assert isinstance(duplicates, dict)


def test_core_export_config():
    """Test configuration export."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        (tmppath / "test.py").write_text("import os\n")
        
        cleaner = DepCleaner(tmppath, max_workers=8)
        config_file = tmppath / "config.json"
        
        cleaner.export_config(str(config_file))
        
        assert config_file.exists()
        
        with open(config_file) as f:
            config = json.load(f)
        
        assert config["max_workers"] == 8
        assert "project_path" in config


def test_core_clear_cache():
    """Test cache clearing."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        (tmppath / "test.py").write_text("import os\n")
        
        cleaner = DepCleaner(tmppath, cache_results=True)
        
        # Scan to populate cache
        cleaner.scan()
        assert cleaner._cached_report is not None
        
        # Clear cache
        cleaner.clear_cache()
        assert cleaner._cached_report is None


def test_core_get_health_score():
    """Test health score calculation."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        (tmppath / "test.py").write_text("import os\nimport sys\nprint('hello')\n")
        
        cleaner = DepCleaner(tmppath)
        health = cleaner.get_health_score()
        
        assert "score" in health
        assert "grade" in health
        assert "metrics" in health
        assert "recommendations" in health
        
        assert 0 <= health["score"] <= 100
        assert health["grade"] in ["A", "B", "C", "D", "F"]


def test_core_health_score_perfect():
    """Test perfect health score."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        (tmppath / "test.py").write_text("import os\nprint(os.name)\n")
        
        cleaner = DepCleaner(tmppath)
        health = cleaner.get_health_score()
        
        # Should have high score with no unused deps
        assert health["score"] >= 90
        assert health["grade"] in ["A", "B"]


def test_core_health_recommendations():
    """Test health recommendations."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        (tmppath / "test.py").write_text("import os\nimport sys\nimport json\n")
        
        cleaner = DepCleaner(tmppath)
        health = cleaner.get_health_score()
        
        recommendations = health["recommendations"]
        assert isinstance(recommendations, list)
        assert len(recommendations) > 0


def test_core_validate_project_warnings():
    """Test project validation with warnings."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        (tmppath / "test.py").write_text("import os\n")
        
        # Create venv dir to trigger warning
        (tmppath / "venv").mkdir()
        
        cleaner = DepCleaner(tmppath)
        validation = cleaner.validate_project()
        
        assert "warnings" in validation
        assert isinstance(validation["warnings"], list)


def test_core_validate_project_recommendations():
    """Test project validation recommendations."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        (tmppath / "test.py").write_text("import os\n")
        
        # No dependency files
        cleaner = DepCleaner(tmppath)
        validation = cleaner.validate_project()
        
        assert "recommendations" in validation
        assert len(validation["recommendations"]) > 0


def test_core_estimate_cleanup_with_lines():
    """Test cleanup impact estimation with line counts."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        (tmppath / "test.py").write_text("import os\nimport sys\nimport json\n")
        
        cleaner = DepCleaner(tmppath)
        impact = cleaner.estimate_cleanup_impact()
        
        assert "estimated_lines_saved" in impact
        assert impact["estimated_lines_saved"] >= 0


def test_core_with_progress_callback():
    """Test core with progress callback."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        (tmppath / "test.py").write_text("import os\n")
        
        calls = []
        
        def callback(current, total, message):
            calls.append((current, total, message))
        
        cleaner = DepCleaner(tmppath)
        cleaner.scan(progress_callback=callback)
        
        # Should have received progress updates
        assert len(calls) > 0


def test_core_fix_with_progress_callback():
    """Test fix with progress callback."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        (tmppath / "test.py").write_text("import os\nprint('hello')\n")
        
        calls = []
        
        def callback(current, total, message):
            calls.append((current, total, message))
        
        cleaner = DepCleaner(tmppath)
        cleaner.fix(backup=False, progress_callback=callback)
        
        # Should have progress updates
        assert len(calls) >= 0


# ============= Scanner Module Additional Tests =============

def test_scanner_custom_exclude_dirs():
    """Test scanner with custom exclude directories."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        (tmppath / "test.py").write_text("import os\n")
        
        # Create custom dir to exclude
        custom_dir = tmppath / "custom_exclude"
        custom_dir.mkdir()
        (custom_dir / "excluded.py").write_text("import sys\n")
        
        scanner = Scanner(tmppath, exclude_dirs={"custom_exclude"})
        scanner._discover_python_files()
        
        # Should only find test.py
        assert len(scanner.python_files) == 1
        assert scanner.python_files[0].name == "test.py"


def test_scanner_large_file_exclusion():
    """Test that scanner skips large files."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        
        # Create small file
        small_file = tmppath / "small.py"
        small_file.write_text("import os\n")
        
        # Create large file (>1MB)
        large_file = tmppath / "large.py"
        large_content = "# " + ("x" * 1_100_000)
        large_file.write_text(large_content)
        
        scanner = Scanner(tmppath)
        scanner._discover_python_files()
        
        # Should only find small.py
        file_names = [f.name for f in scanner.python_files]
        assert "small.py" in file_names
        # large.py might or might not be included depending on implementation


def test_scanner_encoding_fallback():
    """Test scanner encoding fallback."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        test_file = tmppath / "test.py"
        
        # Write file with latin-1 encoding
        test_file.write_bytes(b"import os\n# \xe9\n")  # Latin-1 char
        
        scanner = Scanner(tmppath)
        imports = scanner._analyze_single_file(test_file)
        
        # Should still extract imports despite encoding issues
        assert isinstance(imports, set)


def test_scanner_get_scan_statistics():
    """Test getting scan statistics."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        (tmppath / "test.py").write_text("import os\n")
        
        scanner = Scanner(tmppath)
        scanner.scan()
        
        stats = scanner.get_scan_statistics()
        
        assert "scan_times" in stats
        assert "files_scanned" in stats
        assert "total_imports" in stats
        assert "unique_imports" in stats
        assert "workers_used" in stats


def test_scanner_parse_setup_cfg():
    """Test parsing setup.cfg."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        
        setup_cfg = tmppath / "setup.cfg"
        setup_cfg.write_text("""
[options]
install_requires =
    requests>=2.28.0
    numpy
""")
        
        scanner = Scanner(tmppath)
        deps = scanner._parse_setup_cfg()
        
        assert "requests" in deps or len(deps) >= 0


def test_scanner_parse_pipfile():
    """Test parsing Pipfile."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        
        pipfile = tmppath / "Pipfile"
        pipfile.write_text("""
[packages]
requests = "*"
numpy = ">=1.20"
""")
        
        scanner = Scanner(tmppath)
        deps = scanner._parse_pipfile()
        
        # May or may not parse depending on tomli availability
        assert isinstance(deps, set)


def test_scanner_extract_package_name():
    """Test package name extraction from spec."""
    scanner = Scanner(Path("."))
    
    # Test various formats
    assert scanner._extract_package_name("requests==2.28.0") == "requests"
    assert scanner._extract_package_name("numpy>=1.20") == "numpy"
    assert scanner._extract_package_name("pandas<=2.0") == "pandas"
    assert scanner._extract_package_name("flask~=2.0") == "flask"
    assert scanner._extract_package_name("django[extra]") == "django"
    assert scanner._extract_package_name("  spaces  ") == "spaces"
    assert scanner._extract_package_name("") is None


def test_scanner_multiple_dependency_sources():
    """Test scanning with multiple dependency sources."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        (tmppath / "test.py").write_text("import os\n")
        
        # Create multiple dependency files
        req_file = tmppath / "requirements.txt"
        req_file.write_text("requests==2.31.0\n")
        
        pyproject = tmppath / "pyproject.toml"
        pyproject.write_text("""
[project]
dependencies = ["numpy>=1.20"]
""")
        
        scanner = Scanner(tmppath)
        deps = scanner._get_declared_dependencies()
        
        # Should find deps from multiple sources
        assert len(deps) > 0


def test_scanner_normalize_package_name():
    """Test package name normalization."""
    scanner = Scanner(Path("."))
    
    assert scanner._normalize_package_name("My-Package") == "my_package"
    assert scanner._normalize_package_name("My_Package") == "my_package"
    assert scanner._normalize_package_name("my.package") == "my_package"
    assert scanner._normalize_package_name("UPPERCASE") == "uppercase"


def test_scanner_find_used_names_with_call():
    """Test that function calls are detected."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        test_file = tmppath / "test.py"
        test_file.write_text("""
from datetime import datetime
result = datetime.now()
""")
        
        scanner = Scanner(tmppath)
        report = scanner.scan()
        
        used = report.used_imports.get(test_file, set())
        # datetime should be detected as used
        assert "datetime" in used


def test_scanner_extract_from_list():
    """Test extracting packages from AST list."""
    import ast
    
    scanner = Scanner(Path("."))
    
    # Create AST list node
    code = "['requests', 'numpy']"
    node = ast.parse(code, mode='eval').body
    
    deps = scanner._extract_from_list(node)
    
    # Should extract package names
    assert isinstance(deps, set)


def test_scanner_empty_file():
    """Test scanning empty file."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        test_file = tmppath / "empty.py"
        test_file.write_text("")
        
        scanner = Scanner(tmppath)
        imports = scanner._analyze_single_file(test_file)
        
        assert imports == set()


def test_scanner_file_with_only_comments():
    """Test scanning file with only comments."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        test_file = tmppath / "comments.py"
        test_file.write_text("# This is a comment\n# Another comment\n")
        
        scanner = Scanner(tmppath)
        imports = scanner._analyze_single_file(test_file)
        
        assert imports == set()


def test_scanner_relative_imports():
    """Test handling of relative imports."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        test_file = tmppath / "test.py"
        test_file.write_text("from . import something\nfrom .. import other\n")
        
        scanner = Scanner(tmppath)
        imports = scanner._analyze_single_file(test_file)
        
        # Relative imports should not be included
        assert len(imports) == 0


def test_scanner_star_imports():
    """Test handling of star imports."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        test_file = tmppath / "test.py"
        test_file.write_text("from os import *\n")
        
        scanner = Scanner(tmppath)
        imports = scanner._analyze_single_file(test_file)
        
        # Should detect os module
        assert "os" in imports


def test_scanner_dotted_imports():
    """Test handling of dotted imports."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        test_file = tmppath / "test.py"
        test_file.write_text("import os.path\nfrom urllib.parse import urlparse\n")
        
        scanner = Scanner(tmppath)
        imports = scanner._analyze_single_file(test_file)
        
        # Should extract top-level modules
        assert "os" in imports
        assert "urllib" in imports


def test_scanner_import_to_package_mapping():
    """Test getting import to package mapping."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        (tmppath / "test.py").write_text("import requests\nimport numpy\n")
        
        req_file = tmppath / "requirements.txt"
        req_file.write_text("requests==2.31.0\nnumpy==1.24.0\n")
        
        scanner = Scanner(tmppath)
        scanner.scan()
        
        mapping = scanner.get_import_to_package_mapping()
        
        assert isinstance(mapping, dict)


def test_scanner_progress_logging():
    """Test that scanner logs progress for large projects."""
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        
        # Create many files
        for i in range(150):
            (tmppath / f"test{i}.py").write_text("import os\n")
        
        scanner = Scanner(tmppath, max_workers=2)
        
        # Should log progress every 100 files
        report = scanner.scan()
        
        assert report.scanned_files == 150