"""
Unit tests for FileUploadValidator.

Tests cover all validation methods:
- File size validation
- File count validation
- MIME type validation
- PDF structure validation
- Comprehensive upload validation
"""

from unittest.mock import patch

from local_deep_research.security.file_upload_validator import (
    FileUploadValidator,
)


class TestValidateFileSize:
    """Tests for validate_file_size method.

    Oversized-path tests patch ``MAX_FILE_SIZE`` to a small value so they
    can exercise the rejection branch without allocating multi-GB buffers
    in CI.
    """

    def test_valid_size_via_content_length(self):
        """Test that valid size passes via Content-Length header."""
        is_valid, error = FileUploadValidator.validate_file_size(
            content_length=1024 * 1024  # 1MB
        )
        assert is_valid is True
        assert error is None

    def test_valid_size_via_file_content(self):
        """Test that valid size passes via actual file content."""
        content = b"x" * (1024 * 1024)  # 1MB
        is_valid, error = FileUploadValidator.validate_file_size(
            content_length=None, file_content=content
        )
        assert is_valid is True
        assert error is None

    def test_oversized_via_content_length(self):
        """Test that oversized file is rejected via Content-Length."""
        with patch.object(
            FileUploadValidator, "MAX_FILE_SIZE", 50 * 1024 * 1024
        ):
            is_valid, error = FileUploadValidator.validate_file_size(
                content_length=51 * 1024 * 1024  # 51MB over a 50MB cap
            )
        assert is_valid is False
        assert error is not None
        assert "too large" in error.lower()

    def test_oversized_via_file_content(self):
        """Test that oversized file is rejected via actual content."""
        with patch.object(FileUploadValidator, "MAX_FILE_SIZE", 1024):
            content = b"x" * 2048  # 2KB over a 1KB cap
            is_valid, error = FileUploadValidator.validate_file_size(
                content_length=None, file_content=content
            )
        assert is_valid is False
        assert error is not None
        assert "too large" in error.lower()

    def test_exactly_at_limit(self):
        """Test that file exactly at limit passes."""
        is_valid, error = FileUploadValidator.validate_file_size(
            content_length=FileUploadValidator.MAX_FILE_SIZE
        )
        assert is_valid is True
        assert error is None

    def test_one_byte_over_limit(self):
        """Test that file one byte over limit is rejected."""
        is_valid, error = FileUploadValidator.validate_file_size(
            content_length=FileUploadValidator.MAX_FILE_SIZE + 1
        )
        assert is_valid is False
        assert error is not None

    def test_both_none_returns_valid(self):
        """Test that both None values return valid (no info to validate)."""
        is_valid, error = FileUploadValidator.validate_file_size(
            content_length=None, file_content=None
        )
        assert is_valid is True
        assert error is None


class TestValidateFileCount:
    """Tests for validate_file_count method."""

    def test_valid_file_count(self):
        """Test that valid file count passes."""
        is_valid, error = FileUploadValidator.validate_file_count(10)
        assert is_valid is True
        assert error is None

    def test_single_file_valid(self):
        """Test that single file passes."""
        is_valid, error = FileUploadValidator.validate_file_count(1)
        assert is_valid is True
        assert error is None

    def test_max_files_valid(self):
        """Test that exactly max files passes."""
        is_valid, error = FileUploadValidator.validate_file_count(
            FileUploadValidator.MAX_FILES_PER_REQUEST
        )
        assert is_valid is True
        assert error is None

    def test_too_many_files(self):
        """Test that exceeding max files is rejected."""
        is_valid, error = FileUploadValidator.validate_file_count(
            FileUploadValidator.MAX_FILES_PER_REQUEST + 1
        )
        assert is_valid is False
        assert error is not None
        assert "too many" in error.lower()

    def test_zero_files(self):
        """Test that zero files is rejected."""
        is_valid, error = FileUploadValidator.validate_file_count(0)
        assert is_valid is False
        assert error is not None
        assert "no files" in error.lower()

    def test_negative_count(self):
        """Test that negative count is rejected."""
        is_valid, error = FileUploadValidator.validate_file_count(-1)
        assert is_valid is False
        assert error is not None


class TestValidateMimeType:
    """Tests for validate_mime_type method."""

    def test_valid_pdf_extension_and_magic(self):
        """Test that valid PDF passes."""
        content = b"%PDF-1.4\ntest content"
        is_valid, error = FileUploadValidator.validate_mime_type(
            "test.pdf", content
        )
        assert is_valid is True
        assert error is None

    def test_uppercase_extension_valid(self):
        """Test that uppercase .PDF extension passes."""
        content = b"%PDF-1.4\ntest content"
        is_valid, error = FileUploadValidator.validate_mime_type(
            "test.PDF", content
        )
        assert is_valid is True
        assert error is None

    def test_mixed_case_extension_valid(self):
        """Test that mixed case .Pdf extension passes."""
        content = b"%PDF-1.4\ntest content"
        is_valid, error = FileUploadValidator.validate_mime_type(
            "test.PdF", content
        )
        assert is_valid is True
        assert error is None

    def test_wrong_extension_rejected(self):
        """Test that wrong extension is rejected."""
        content = b"%PDF-1.4\ntest content"
        is_valid, error = FileUploadValidator.validate_mime_type(
            "test.txt", content
        )
        assert is_valid is False
        assert error is not None
        assert "only pdf" in error.lower()

    def test_no_extension_rejected(self):
        """Test that file without extension is rejected."""
        content = b"%PDF-1.4\ntest content"
        is_valid, error = FileUploadValidator.validate_mime_type(
            "testfile", content
        )
        assert is_valid is False
        assert error is not None

    def test_wrong_magic_bytes_rejected(self):
        """Test that wrong magic bytes are rejected."""
        content = b"This is not a PDF"
        is_valid, error = FileUploadValidator.validate_mime_type(
            "test.pdf", content
        )
        assert is_valid is False
        assert error is not None
        assert "signature mismatch" in error.lower()

    def test_empty_content_rejected(self):
        """Test that empty content is rejected."""
        content = b""
        is_valid, error = FileUploadValidator.validate_mime_type(
            "test.pdf", content
        )
        assert is_valid is False
        assert error is not None

    def test_exe_disguised_as_pdf_rejected(self):
        """Test that executable disguised as PDF is rejected."""
        # MZ header for executable
        content = b"MZ" + b"\x00" * 100
        is_valid, error = FileUploadValidator.validate_mime_type(
            "malware.pdf", content
        )
        assert is_valid is False
        assert error is not None


class TestValidatePdfStructure:
    """Tests for validate_pdf_structure method."""

    def test_malformed_pdf_structure(self):
        """Test that malformed PDF structure is rejected."""
        # Has magic bytes but invalid structure
        content = b"%PDF-1.4\nthis is not valid pdf structure"
        is_valid, error = FileUploadValidator.validate_pdf_structure(
            "test.pdf", content
        )
        assert is_valid is False
        assert error is not None
        assert "invalid" in error.lower() or "corrupted" in error.lower()

    def test_truncated_pdf_rejected(self):
        """Test that truncated PDF is rejected."""
        content = b"%PDF-1.4"  # Just the header
        is_valid, error = FileUploadValidator.validate_pdf_structure(
            "test.pdf", content
        )
        assert is_valid is False
        assert error is not None


class TestValidateUpload:
    """Tests for validate_upload comprehensive method."""

    def test_all_validations_run(self):
        """Test that all validations are executed."""
        # Invalid size should fail first. Patch the cap to keep memory low.
        with patch.object(FileUploadValidator, "MAX_FILE_SIZE", 1024):
            large_content = b"%PDF-1.4\n" + (b"x" * 2048)  # 2KB over 1KB cap
            is_valid, error = FileUploadValidator.validate_upload(
                filename="test.pdf",
                file_content=large_content,
            )
        assert is_valid is False
        assert "too large" in error.lower()

    def test_invalid_mime_fails_after_size(self):
        """Test that MIME validation runs after size validation."""
        content = b"not a pdf"
        is_valid, error = FileUploadValidator.validate_upload(
            filename="test.txt",  # Wrong extension
            file_content=content,
        )
        assert is_valid is False
        assert "pdf" in error.lower()

    def test_returns_first_error(self):
        """Test that first validation error stops processing."""
        # Large file with wrong extension - should fail on size first
        with patch.object(FileUploadValidator, "MAX_FILE_SIZE", 1024):
            large_content = b"x" * 2048  # 2KB over 1KB cap
            is_valid, error = FileUploadValidator.validate_upload(
                filename="test.txt",
                file_content=large_content,
            )
        assert is_valid is False
        # Should fail on size before checking extension
        assert "too large" in error.lower()


class TestConstants:
    """Tests for class constants."""

    def test_max_file_size_default_is_3gb(self):
        """Default MAX_FILE_SIZE matches the 3 GB server-config default."""
        expected = 3 * 1024 * 1024 * 1024
        assert FileUploadValidator.MAX_FILE_SIZE == expected

    def test_max_files_is_200(self):
        """Verify MAX_FILES_PER_REQUEST is 200."""
        assert FileUploadValidator.MAX_FILES_PER_REQUEST == 200

    def test_pdf_magic_bytes(self):
        """Verify PDF magic bytes."""
        assert FileUploadValidator.PDF_MAGIC_BYTES == b"%PDF"

    def test_allowed_mime_types(self):
        """Verify allowed MIME types."""
        assert "application/pdf" in FileUploadValidator.ALLOWED_MIME_TYPES
