"""Unit tests for docscan module."""

import json

# Import the module being tested
import sys
from pathlib import Path

import pytest

sys.path.insert(0, str(Path(__file__).parent.parent))
from sfi.docscan.docscan import DocumentScanner, Rule


class TestRule:
    """Test cases for Rule class."""

    def test_rule_initialization_regex(self):
        """Test Rule initialization with regex pattern."""
        rule_data = {
            "name": "test_rule",
            "pattern": r"\d{3}-\d{2}-\d{4}",
            "regex": True,
            "case_sensitive": False,
            "context_lines": 2,
            "description": "Test description",
        }
        rule = Rule(rule_data)

        assert rule.name == "test_rule"
        assert rule.pattern == r"\d{3}-\d{2}-\d{4}"
        assert rule.is_regex is True
        assert rule.case_sensitive is False
        assert rule.context_lines == 2
        assert rule.description == "Test description"
        assert rule.compiled_pattern is not None

    def test_rule_initialization_text(self):
        """Test Rule initialization with simple text pattern."""
        rule_data = {
            "name": "text_rule",
            "pattern": "hello world",
            "regex": False,
            "case_sensitive": True,
            "context_lines": 5,
        }
        rule = Rule(rule_data)

        assert rule.name == "text_rule"
        assert rule.pattern == "hello world"
        assert rule.is_regex is False
        assert rule.case_sensitive is True
        assert rule.context_lines == 5
        assert rule.compiled_pattern is None

    def test_rule_default_values(self):
        """Test Rule with default values."""
        rule_data = {"name": "default_rule", "pattern": "test"}
        rule = Rule(rule_data)

        assert rule.is_regex is False
        assert rule.case_sensitive is False
        assert rule.context_lines == 3
        assert rule.description == ""

    def test_rule_invalid_regex(self):
        """Test Rule with invalid regex pattern."""
        rule_data = {"name": "invalid_rule", "pattern": "[invalid(regex", "regex": True}
        rule = Rule(rule_data)

        # Should not crash, but compiled_pattern should be None
        assert rule.compiled_pattern is None

    def test_search_regex_pattern(self):
        """Test regex search functionality."""
        rule_data = {
            "name": "email_rule",
            "pattern": r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}",
            "regex": True,
            "case_sensitive": False,
        }
        rule = Rule(rule_data)

        text = """
        Contact us at support@example.com or sales@company.org
        Invalid: not-an-email
        """
        matches = rule.search(text)

        assert len(matches) == 2
        assert matches[0]["match"] == "support@example.com"
        assert matches[1]["match"] == "sales@company.org"
        assert matches[0]["type"] == "regex"

    def test_search_text_pattern(self):
        """Test text search functionality."""
        rule_data = {"name": "keyword_rule", "pattern": "confidential", "regex": False, "case_sensitive": False}
        rule = Rule(rule_data)

        text = """
        This document contains CONFIDENTIAL information.
        Please do not share confidential data.
        """
        matches = rule.search(text)

        assert len(matches) == 2
        assert "confidential" in matches[0]["match"].lower()
        assert matches[0]["type"] == "text"

    def test_search_case_sensitive(self):
        """Test case-sensitive search."""
        rule_data = {"name": "case_rule", "pattern": "TEST", "regex": False, "case_sensitive": True}
        rule = Rule(rule_data)

        text = "This is a TEST string with test lower case"
        matches = rule.search(text)

        # Should only match uppercase TEST
        assert len(matches) == 1
        assert matches[0]["match"] == "TEST"

    def test_search_empty_text(self):
        """Test search with empty text."""
        rule_data = {"name": "empty_rule", "pattern": "test"}
        rule = Rule(rule_data)

        matches = rule.search("")
        assert matches == []

    def test_search_no_matches(self):
        """Test search with no matches found."""
        rule_data = {"name": "no_match_rule", "pattern": "xyz123"}
        rule = Rule(rule_data)

        text = "This is a test string without the pattern"
        matches = rule.search(text)

        assert matches == []

    def test_get_context(self):
        """Test context extraction."""
        rule_data = {"name": "context_rule", "pattern": "test", "context_lines": 2}
        rule = Rule(rule_data)

        lines = ["Line 1", "Line 2", "Line 3 with test pattern", "Line 4", "Line 5", "Line 6"]

        context = rule._get_context(lines, 2)  # Line 3 is at index 2

        assert len(context) == 5  # 2 lines before + match line + 2 lines after
        assert "Line 1" in context
        assert "Line 3 with test pattern" in context
        assert "Line 5" in context

    def test_get_context_boundary(self):
        """Test context extraction at boundaries."""
        rule_data = {"name": "boundary_rule", "pattern": "test", "context_lines": 3}
        rule = Rule(rule_data)

        lines = ["Line 1 with test", "Line 2", "Line 3"]

        context = rule._get_context(lines, 0)  # First line

        assert len(context) == 3  # Only 3 lines total available
        assert "Line 1 with test" in context


class TestDocumentScanner:
    """Test cases for DocumentScanner class."""

    @pytest.fixture
    def test_rules(self):
        """Create test rules for scanning."""
        return [
            Rule({
                "name": "email",
                "pattern": r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}",
                "regex": True,
                "context_lines": 1,
            }),
            Rule({"name": "phone", "pattern": r"\d{3}-\d{3}-\d{4}", "regex": True, "context_lines": 1}),
        ]

    @pytest.fixture
    def test_directory(self, tmp_path):
        """Create a temporary test directory with test files."""
        # Create test text file
        test_file = tmp_path / "test.txt"
        test_file.write_text("""
        Contact Information:
        Email: john.doe@example.com
        Phone: 555-123-4567

        Support:
        Email: support@test.org
        Phone: 555-987-6543
        """)

        # Create JSON file
        json_file = tmp_path / "data.json"
        json_file.write_text(json.dumps({"contact": "info@company.com", "phone": "555-555-5555"}))

        return tmp_path

    def test_scanner_initialization(self, test_directory, test_rules):
        """Test DocumentScanner initialization."""
        scanner = DocumentScanner(test_directory, test_rules, ["txt", "json"], use_pdf_ocr=False)

        assert scanner.input_dir == test_directory
        assert len(scanner.rules) == 2
        assert scanner.file_types == ["txt", "json"]
        assert scanner.use_pdf_ocr is False
        assert scanner.results == []

    def test_collect_files(self, test_directory):
        """Test file collection functionality."""
        scanner = DocumentScanner(test_directory, [Rule({"name": "test", "pattern": "test"})], ["txt", "json"])

        files = scanner._collect_files()

        assert len(files) == 2
        file_names = [f.name for f in files]
        assert "test.txt" in file_names
        assert "data.json" in file_names

    def test_extract_text(self, test_directory):
        """Test text extraction from plain text file."""
        scanner = DocumentScanner(test_directory, [Rule({"name": "test", "pattern": "test"})], ["txt"])

        text_file = test_directory / "test.txt"
        text, metadata = scanner._extract_text(text_file)

        assert len(text) > 0
        assert "john.doe@example.com" in text
        assert metadata.get("encoding") in ["utf-8", "latin-1", "cp1252"]

    def test_scan_single_file(self, test_directory, test_rules):
        """Test scanning a single file."""
        scanner = DocumentScanner(test_directory, test_rules, ["txt"])

        test_file = test_directory / "test.txt"
        result = scanner._scan_file(test_file)

        assert result is not None
        assert "file_path" in result
        assert "file_type" in result
        assert "matches" in result
        assert result["file_type"] == "txt"
        assert len(result["matches"]) > 0

    def test_scan_with_no_matches(self, test_directory, tmp_path):
        """Test scanning file with no matches."""
        scanner = DocumentScanner(test_directory, [Rule({"name": "nomatch", "pattern": "xyz123"})], ["txt"])

        # Create file with no matches
        no_match_file = tmp_path / "nomatch.txt"
        no_match_file.write_text("This file has no matching patterns")

        result = scanner._scan_file(no_match_file)
        assert result == {}

    def test_scan_results_structure(self, test_directory, test_rules):
        """Test that scan results have correct structure."""
        scanner = DocumentScanner(test_directory, test_rules, ["txt", "json"])

        results = scanner.scan(threads=1)

        assert "scan_info" in results
        assert "rules" in results
        assert "matches" in results

        assert "input_directory" in results["scan_info"]
        assert "scan_time" in results["scan_info"]
        assert "total_files" in results["scan_info"]
        assert "files_with_matches" in results["scan_info"]

    def test_scan_info_accuracy(self, test_directory, test_rules):
        """Test that scan info accurately reflects scan results."""
        scanner = DocumentScanner(test_directory, test_rules, ["txt", "json"])

        results = scanner.scan(threads=1)

        assert results["scan_info"]["total_files"] == 2
        assert results["scan_info"]["rules_count"] == 2
        assert results["scan_info"]["files_with_matches"] >= 1

    def test_scan_with_multiple_file_types(self, test_directory, test_rules):
        """Test scanning multiple file types."""
        scanner = DocumentScanner(test_directory, test_rules, ["txt", "json", "md"])

        # Create markdown file
        md_file = test_directory / "README.md"
        md_file.write_text("Contact: hello@world.com")

        results = scanner.scan(threads=1)

        assert results["scan_info"]["total_files"] == 3

    def test_match_context_extraction(self, test_directory):
        """Test that context is properly extracted for matches."""
        scanner = DocumentScanner(
            test_directory,
            [
                Rule({
                    "name": "email",
                    "pattern": r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}",
                    "regex": True,
                    "context_lines": 2,
                })
            ],
            ["txt"],
        )

        results = scanner.scan(threads=1)

        if results["matches"]:
            file_match = results["matches"][0]
            if file_match["matches"]:
                match = file_match["matches"][0]
                assert "context" in match
                assert isinstance(match["context"], list)


class TestIntegration:
    """Integration tests for complete workflows."""

    def test_complete_scan_workflow(self, tmp_path):
        """Test complete scanning workflow from rules to results."""
        # Create test directory with files
        test_dir = tmp_path / "docs"
        test_dir.mkdir()

        # Create test file
        test_file = test_dir / "document.txt"
        test_file.write_text("""
        Project Documentation

        Contact:
        - Email: admin@example.com
        - Phone: 555-123-4567

        Important Dates:
        - Start: 2024-01-15
        - End: 2024-12-31
        """)

        # Create rules
        rules = [
            Rule({
                "name": "emails",
                "pattern": r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}",
                "regex": True,
                "context_lines": 2,
            }),
            Rule({"name": "dates", "pattern": r"\d{4}-\d{2}-\d{2}", "regex": True, "context_lines": 1}),
        ]

        # Scan documents
        scanner = DocumentScanner(test_dir, rules, ["txt"])
        results = scanner.scan(threads=1)

        # Verify results
        assert results["scan_info"]["total_files"] == 1
        assert results["scan_info"]["files_with_matches"] == 1
        assert len(results["matches"]) == 1

        # Verify matches
        file_matches = results["matches"][0]
        assert len(file_matches["matches"]) >= 2  # At least email and date

        # Check for email match
        email_matches = [m for m in file_matches["matches"] if m["rule_name"] == "emails"]
        assert len(email_matches) > 0
        assert "example.com" in email_matches[0]["match"]

        # Check for date match
        date_matches = [m for m in file_matches["matches"] if m["rule_name"] == "dates"]
        assert len(date_matches) > 0

    def test_save_results_to_json(self, tmp_path):
        """Test saving scan results to JSON file."""
        # Create test data
        test_dir = tmp_path / "test_docs"
        test_dir.mkdir()

        test_file = test_dir / "test.txt"
        test_file.write_text("Contact: test@example.com")

        rules = [Rule({"name": "email", "pattern": r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}", "regex": True})]

        # Scan
        scanner = DocumentScanner(test_dir, rules, ["txt"])
        results = scanner.scan(threads=1)

        # Save to JSON
        output_file = test_dir / "results.json"
        with open(output_file, "w", encoding="utf-8") as f:
            json.dump(results, f, indent=2)

        # Verify file was created and is valid JSON
        assert output_file.exists()

        with open(output_file, encoding="utf-8") as f:
            loaded_results = json.load(f)

        assert loaded_results == results
        assert "scan_info" in loaded_results
        assert "matches" in loaded_results


class TestErrorHandling:
    """Test error handling and edge cases."""

    def test_scan_nonexistent_directory(self, tmp_path):
        """Test scanning a non-existent directory."""
        nonexistent_dir = tmp_path / "nonexistent"

        rules = [Rule({"name": "test", "pattern": "test"})]

        scanner = DocumentScanner(nonexistent_dir, rules, ["txt"])
        results = scanner.scan(threads=1)

        # Should complete without crashing
        assert results["scan_info"]["total_files"] == 0
        assert results["scan_info"]["files_with_matches"] == 0

    def test_scan_empty_directory(self, tmp_path):
        """Test scanning an empty directory."""
        empty_dir = tmp_path / "empty"
        empty_dir.mkdir()

        rules = [Rule({"name": "test", "pattern": "test"})]

        scanner = DocumentScanner(empty_dir, rules, ["txt"])
        results = scanner.scan(threads=1)

        assert results["scan_info"]["total_files"] == 0
        assert results["matches"] == []

    def test_scan_with_no_files_matching_type(self, tmp_path):
        """Test scanning directory with no files of specified type."""
        test_dir = tmp_path / "test"
        test_dir.mkdir()

        # Create a PDF file but scan for TXT only
        pdf_file = test_dir / "test.pdf"
        pdf_file.write_text("fake pdf content")

        rules = [Rule({"name": "test", "pattern": "test"})]

        scanner = DocumentScanner(test_dir, rules, ["txt"])
        results = scanner.scan(threads=1)

        assert results["scan_info"]["total_files"] == 0


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
