"""
Integration tests for license detection and compliance checking.

These tests verify the full end-to-end license detection pipeline including:
- License detection from file headers
- SBOM generation with license metadata
- Commercial use warnings
- AGPL detection
- Unlicensed dataset detection
- Mixed license scenarios
"""

import json
import os
from pathlib import Path
from typing import Any

import pytest

from modelaudit.core import scan_model_directory_or_file
from modelaudit.integrations.license_checker import (
    check_commercial_use_warnings,
    collect_license_metadata,
    detect_agpl_components,
    detect_unlicensed_datasets,
    scan_for_license_headers,
)
from modelaudit.integrations.sbom_generator import generate_sbom


class TestLicenseIntegration:
    """Integration tests for license detection and compliance."""

    @pytest.fixture
    def test_data_dir(self):
        """Return path to integration test data."""
        return Path(__file__).parent.parent / "assets/scenarios/license_scenarios"

    def test_mit_model_clean_license_detection(self, test_data_dir: Any) -> None:
        """Test that MIT licensed model is properly detected without warnings."""
        mit_dir = test_data_dir / "mit_model"

        # Scan the MIT model directory
        results = scan_model_directory_or_file(str(mit_dir), skip_file_types=False)

        # Should have no license warnings
        license_warnings = check_commercial_use_warnings(results)
        [w for w in license_warnings if w.get("severity") != "debug"]

        # MIT is permissive, should not trigger warnings
        agpl_warnings = [w for w in license_warnings if "AGPL" in w.get("message", "")]
        nc_warnings = [w for w in license_warnings if "NonCommercial" in w.get("message", "")]

        assert len(agpl_warnings) == 0, "MIT model should not trigger AGPL warnings"
        assert len(nc_warnings) == 0, "MIT model should not trigger non-commercial warnings"

        # Check that MIT license is detected in metadata
        file_metadata = results.file_metadata if hasattr(results, "file_metadata") else {}
        license_detected = False
        for _file_path, metadata in file_metadata.items():
            licenses = metadata.get("license_info", [])
            for license_info in licenses:
                if isinstance(license_info, dict):
                    spdx_id = license_info.get("spdx_id")
                    if spdx_id == "MIT":
                        license_detected = True
                        assert license_info.get("commercial_allowed") is True

        # At least one file should have MIT license detected
        # (either from header in model.py or config.json)
        # Note: This might not always be true depending on file scanning, so we'll be lenient
        print(f"MIT license detected: {license_detected}")
        print(f"File metadata: {json.dumps(file_metadata, indent=2, default=str)}")

    def test_agpl_component_triggers_warnings(self, test_data_dir: Any) -> None:
        """Test that AGPL component triggers proper warnings."""
        agpl_dir = test_data_dir / "agpl_component"

        # Scan the AGPL component directory
        results = scan_model_directory_or_file(str(agpl_dir), skip_file_types=False)

        # Should trigger AGPL warnings
        license_warnings = check_commercial_use_warnings(results)

        # Check for AGPL warning
        agpl_warnings = [w for w in license_warnings if "AGPL" in w.get("message", "")]
        assert len(agpl_warnings) >= 1, "AGPL component should trigger AGPL warnings"

        agpl_warning = agpl_warnings[0]
        assert agpl_warning["severity"] == "warning"
        assert "network use restrictions" in agpl_warning["message"]
        assert "source code disclosure" in agpl_warning["details"]["impact"]

        # Check for copyleft warning
        copyleft_warnings = [w for w in license_warnings if "copyleft" in w.get("message", "")]
        assert len(copyleft_warnings) >= 1, "AGPL should trigger copyleft warnings"

    def test_unlicensed_dataset_detection(self, test_data_dir: Any) -> None:
        """Test detection of unlicensed datasets."""
        unlicensed_dir = test_data_dir / "unlicensed_dataset"

        # Scan the unlicensed dataset directory
        results = scan_model_directory_or_file(str(unlicensed_dir), skip_file_types=False)

        # Should trigger unlicensed dataset warnings
        license_warnings = check_commercial_use_warnings(results)

        dataset_warnings = [w for w in license_warnings if "unspecified licenses" in w.get("message", "")]
        assert len(dataset_warnings) >= 1, "Unlicensed datasets should trigger warnings"

        dataset_warning = dataset_warnings[0]
        assert dataset_warning["severity"] == "info"
        assert "Verify data usage rights" in dataset_warning["message"]

    def test_mixed_licenses_detection(self, test_data_dir: Any) -> None:
        """Test detection of mixed license scenarios."""
        mixed_dir = test_data_dir / "mixed_licenses"

        # Scan the mixed licenses directory
        results = scan_model_directory_or_file(str(mixed_dir), skip_file_types=False)

        # Should detect multiple license types
        license_warnings = check_commercial_use_warnings(results)

        # Should detect non-commercial license in the CC-NC dataset
        nc_warnings = [w for w in license_warnings if "Non-commercial" in w.get("message", "")]
        # Note: This might not trigger if the CC-NC pattern isn't detected in the JSON content

        # Should detect GPL copyleft
        copyleft_warnings = [w for w in license_warnings if "copyleft" in w.get("message", "")]

        print(
            f"Mixed licenses warnings: {json.dumps(license_warnings, indent=2, default=str)}",
        )
        print(f"Non-commercial warnings: {len(nc_warnings)}")
        print(f"Copyleft warnings: {len(copyleft_warnings)}")

    def test_sbom_generation_with_license_metadata(self, test_data_dir: Any) -> None:
        """Test SBOM generation includes license metadata."""
        mit_dir = test_data_dir / "mit_model"

        # Scan and generate SBOM
        results = scan_model_directory_or_file(str(mit_dir), skip_file_types=False)
        sbom_json = generate_sbom([str(mit_dir)], results)

        # Parse SBOM
        sbom_data = json.loads(sbom_json)

        # Should have components
        assert "components" in sbom_data
        components = sbom_data["components"]
        assert len(components) > 0, "SBOM should contain components"

        # Check that components have properties including license info
        has_license_properties = False
        for component in components:
            properties = component.get("properties", [])
            for prop in properties:
                if prop.get("name") in [
                    "is_dataset",
                    "is_model",
                    "copyright_holders",
                    "license_files_found",
                ]:
                    has_license_properties = True
                    break

        print(f"SBOM components: {len(components)}")
        print(f"Has license properties: {has_license_properties}")
        print(
            f"Sample component: {json.dumps(components[0] if components else {}, indent=2)}",
        )

    def test_license_file_detection(self, test_data_dir: Any) -> None:
        """Test detection of LICENSE files in directories."""
        mixed_dir = test_data_dir / "mixed_licenses"

        # Test license metadata collection on a file in directory with LICENSE file
        test_file = mixed_dir / "mixed_model.pkl"
        metadata = collect_license_metadata(str(test_file))

        # Should find nearby license file
        license_files = metadata.get("license_files_nearby", [])
        assert len(license_files) > 0, "Should detect LICENSE file in directory"

        # Should detect as model file
        assert metadata.get("is_model") is True, "Should detect .pkl as model file"
        assert metadata.get("is_dataset") is True, "Should also detect .pkl as potential dataset"

    def test_agpl_component_detection_function(self, test_data_dir: Any) -> None:
        """Test the specific AGPL detection function."""
        agpl_dir = test_data_dir / "agpl_component"

        # Scan directory
        results = scan_model_directory_or_file(str(agpl_dir), skip_file_types=False)

        # Test AGPL detection function directly
        agpl_files = detect_agpl_components(results)

        # Should find AGPL files
        agpl_python_files = [f for f in agpl_files if f.endswith(".py")]
        assert len(agpl_python_files) > 0, "Should detect AGPL Python files"

        print(f"AGPL files detected: {agpl_files}")

    def test_unlicensed_dataset_detection_function(self, test_data_dir: Any) -> None:
        """Test the specific unlicensed dataset detection function."""
        unlicensed_dir = test_data_dir / "unlicensed_dataset"

        # Get all files in directory
        files = []
        for root, _, filenames in os.walk(unlicensed_dir):
            for filename in filenames:
                files.append(os.path.join(root, filename))

        # Test unlicensed dataset detection
        unlicensed = detect_unlicensed_datasets(files)

        # Should detect unlicensed datasets
        assert len(unlicensed) > 0, "Should detect unlicensed dataset files"

        # Should include our test files
        unlicensed_names = [Path(f).name for f in unlicensed]
        assert any(name.endswith(".json") for name in unlicensed_names), "Should detect JSON dataset"
        assert any(name.endswith(".csv") for name in unlicensed_names), "Should detect CSV dataset"

        print(f"Unlicensed datasets: {unlicensed}")

    def test_license_header_scanning(self, test_data_dir: Any) -> None:
        """Test license header scanning on individual files."""
        # Test MIT license detection
        mit_file = test_data_dir / "mit_model" / "model.py"
        mit_licenses = scan_for_license_headers(str(mit_file))

        # Should detect MIT license
        assert len(mit_licenses) > 0, "Should detect MIT license in header"
        mit_license = mit_licenses[0]
        assert mit_license.spdx_id == "MIT"
        assert mit_license.commercial_allowed is True

        # Test AGPL license detection
        agpl_file = test_data_dir / "agpl_component" / "neural_network.py"
        agpl_licenses = scan_for_license_headers(str(agpl_file))

        # Should detect AGPL license
        assert len(agpl_licenses) > 0, "Should detect AGPL license in header"
        agpl_license = agpl_licenses[0]
        assert agpl_license.spdx_id is not None and "AGPL" in agpl_license.spdx_id
        assert agpl_license.commercial_allowed is True  # AGPL allows commercial use but with obligations

    def test_end_to_end_cli_integration(self, test_data_dir: Any) -> None:
        """Test the full CLI integration with license warnings."""

        # Test with AGPL component (should trigger warnings and exit code 1)
        agpl_dir = str(test_data_dir / "agpl_component")

        # Capture the scan results by calling the core function directly
        # (since scan_command calls sys.exit)
        results = scan_model_directory_or_file(agpl_dir, skip_file_types=False)

        # Should have license warnings
        license_warnings = check_commercial_use_warnings(results)
        assert len(license_warnings) > 0, "Should generate license warnings"

        # Issues should include license warnings
        all_issues = results.issues if hasattr(results, "issues") else []
        license_issues = [issue for issue in all_issues if hasattr(issue, "type") and issue.type == "license_warning"]
        assert len(license_issues) > 0, "Should have license warning issues"

        # Should have AGPL warning
        agpl_issues = [issue for issue in license_issues if "AGPL" in getattr(issue, "message", "")]
        assert len(agpl_issues) > 0, "Should have AGPL warning issues"

    def test_copyright_detection(self, test_data_dir: Any) -> None:
        """Test copyright notice detection."""
        from modelaudit.integrations.license_checker import extract_copyright_notices

        # Test copyright detection in MIT file
        mit_file = test_data_dir / "mit_model" / "model.py"
        copyrights = extract_copyright_notices(str(mit_file))

        # Should detect copyright notice
        assert len(copyrights) > 0, "Should detect copyright notice"
        copyright_info = copyrights[0]
        assert copyright_info.year is not None and "2024" in copyright_info.year
        assert "Test Model Company" in copyright_info.holder

    @pytest.mark.integration
    def test_large_file_license_scanning(self, test_data_dir: Any) -> None:
        """Test license scanning on larger files (like the numpy array)."""
        # The embeddings.npy file we created should be substantial
        embeddings_file = test_data_dir / "unlicensed_dataset" / "embeddings.npy"

        if embeddings_file.exists():
            file_size = embeddings_file.stat().st_size
            print(f"Embeddings file size: {file_size} bytes")

            # Should be detected as unlicensed dataset if large enough
            unlicensed = detect_unlicensed_datasets([str(embeddings_file)])
            if file_size > 100 * 1024:  # > 100KB
                assert str(embeddings_file) in unlicensed, "Large files should be flagged as unlicensed datasets"


class TestLicenseScenarios:
    """Test specific license scenarios and edge cases."""

    def test_license_pattern_recognition(self) -> None:
        """Test license pattern recognition with various formats."""
        import re

        from modelaudit.integrations.license_checker import LICENSE_PATTERNS

        test_texts = [
            "MIT License",
            "Licensed under the Apache License, Version 2.0",
            "SPDX-License-Identifier: MIT",
            "GNU Affero General Public License v3.0",
            "Creative Commons Attribution NonCommercial 4.0",
        ]

        detected_licenses = []
        for text in test_texts:
            for pattern, info in LICENSE_PATTERNS.items():
                if re.search(pattern, text, re.IGNORECASE):
                    detected_licenses.append(info["spdx_id"])
                    break

        assert "MIT" in detected_licenses
        assert "Apache-2.0" in detected_licenses
        assert "AGPL-3.0" in detected_licenses

    def test_commercial_use_classification(self) -> None:
        """Test commercial use classification for different licenses."""
        from modelaudit.integrations.license_checker import LICENSE_PATTERNS

        # Permissive licenses should allow commercial use
        permissive_patterns = ["MIT", "Apache-2.0", "BSD-3-Clause"]
        for _pattern_key, info in LICENSE_PATTERNS.items():
            if info["spdx_id"] in permissive_patterns:
                assert info["commercial_allowed"] is True, f"{info['spdx_id']} should allow commercial use"

        # Non-commercial licenses should not allow commercial use
        nc_patterns = ["CC-BY-NC"]
        for _pattern_key, info in LICENSE_PATTERNS.items():
            spdx_id = info["spdx_id"]
            if spdx_id and isinstance(spdx_id, str) and any(nc in spdx_id for nc in nc_patterns):
                assert info["commercial_allowed"] is False, f"{info['spdx_id']} should not allow commercial use"

    def test_ml_model_directory_detection(self) -> None:
        """Test ML model directory detection logic."""
        from modelaudit.integrations.license_checker import _is_ml_model_directory

        # Should detect ML model directory
        ml_files = ["config.json", "pytorch_model.bin", "tokenizer_config.json"]
        assert _is_ml_model_directory(ml_files) is True

        # Should not detect regular directory
        regular_files = ["data.csv", "readme.txt", "script.py"]
        assert _is_ml_model_directory(regular_files) is False


if __name__ == "__main__":
    # Allow running individual tests for debugging
    pytest.main([__file__, "-v"])
