"""
Tests for FileUploadValidator security module.
"""

from unittest.mock import Mock, patch

from src.local_deep_research.security.file_upload_validator import (
    FileUploadValidator,
)


class TestFileUploadValidatorConstants:
    """Tests for FileUploadValidator constants."""

    def test_max_file_size_defined(self):
        """MAX_FILE_SIZE is defined and reasonable."""
        assert FileUploadValidator.MAX_FILE_SIZE > 0
        # 50MB default
        assert FileUploadValidator.MAX_FILE_SIZE == 50 * 1024 * 1024

    def test_max_files_per_request_defined(self):
        """MAX_FILES_PER_REQUEST is defined and reasonable."""
        assert FileUploadValidator.MAX_FILES_PER_REQUEST > 0
        assert FileUploadValidator.MAX_FILES_PER_REQUEST == 200

    def test_pdf_magic_bytes_correct(self):
        """PDF_MAGIC_BYTES is correct."""
        assert FileUploadValidator.PDF_MAGIC_BYTES == b"%PDF"

    def test_allowed_mime_types_includes_pdf(self):
        """ALLOWED_MIME_TYPES includes PDF."""
        assert "application/pdf" in FileUploadValidator.ALLOWED_MIME_TYPES


class TestValidateFileSize:
    """Tests for FileUploadValidator.validate_file_size()."""

    def test_valid_content_length(self):
        """Accepts valid content length."""
        is_valid, error = FileUploadValidator.validate_file_size(1000)
        assert is_valid is True
        assert error is None

    def test_valid_file_content(self):
        """Accepts valid file content size."""
        content = b"x" * 1000
        is_valid, error = FileUploadValidator.validate_file_size(None, content)
        assert is_valid is True
        assert error is None

    def test_content_length_exceeds_max(self):
        """Rejects content length exceeding max."""
        large_size = FileUploadValidator.MAX_FILE_SIZE + 1
        is_valid, error = FileUploadValidator.validate_file_size(large_size)
        assert is_valid is False
        assert "too large" in error.lower()

    def test_file_content_exceeds_max(self):
        """Rejects file content exceeding max."""
        large_content = b"x" * (FileUploadValidator.MAX_FILE_SIZE + 1)
        is_valid, error = FileUploadValidator.validate_file_size(
            None, large_content
        )
        assert is_valid is False
        assert "too large" in error.lower()

    def test_both_none_is_valid(self):
        """Accepts when both parameters are None."""
        is_valid, error = FileUploadValidator.validate_file_size(None, None)
        assert is_valid is True
        assert error is None

    def test_zero_content_length_is_valid(self):
        """Accepts zero content length."""
        is_valid, error = FileUploadValidator.validate_file_size(0)
        assert is_valid is True
        assert error is None

    def test_error_message_includes_size(self):
        """Error message includes file size information."""
        large_size = 60 * 1024 * 1024  # 60MB
        is_valid, error = FileUploadValidator.validate_file_size(large_size)
        assert is_valid is False
        assert "60" in error or "MB" in error


class TestValidateFileCount:
    """Tests for FileUploadValidator.validate_file_count()."""

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

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

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

    def test_exceeds_max_files(self):
        """Rejects count exceeding max."""
        is_valid, error = FileUploadValidator.validate_file_count(
            FileUploadValidator.MAX_FILES_PER_REQUEST + 1
        )
        assert is_valid is False
        assert "too many" in error.lower()

    def test_zero_files_invalid(self):
        """Rejects zero files."""
        is_valid, error = FileUploadValidator.validate_file_count(0)
        assert is_valid is False
        assert "no files" in error.lower()

    def test_negative_files_invalid(self):
        """Rejects negative file count."""
        is_valid, error = FileUploadValidator.validate_file_count(-1)
        assert is_valid is False
        assert "no files" in error.lower()


class TestValidateMimeType:
    """Tests for FileUploadValidator.validate_mime_type()."""

    def test_valid_pdf(self):
        """Accepts valid PDF file."""
        content = b"%PDF-1.4 test content"
        is_valid, error = FileUploadValidator.validate_mime_type(
            "document.pdf", content
        )
        assert is_valid is True
        assert error is None

    def test_valid_pdf_uppercase_extension(self):
        """Accepts PDF with uppercase extension."""
        content = b"%PDF-1.4 test content"
        is_valid, error = FileUploadValidator.validate_mime_type(
            "document.PDF", content
        )
        assert is_valid is True
        assert error is None

    def test_invalid_extension(self):
        """Rejects non-PDF extension."""
        content = b"%PDF-1.4 test content"
        is_valid, error = FileUploadValidator.validate_mime_type(
            "document.txt", content
        )
        assert is_valid is False
        assert "only pdf" in error.lower()

    def test_invalid_magic_bytes(self):
        """Rejects file with wrong magic bytes."""
        content = b"PK\x03\x04"  # ZIP magic bytes
        is_valid, error = FileUploadValidator.validate_mime_type(
            "document.pdf", content
        )
        assert is_valid is False
        assert "signature" in error.lower()

    def test_empty_content(self):
        """Rejects empty file content."""
        is_valid, error = FileUploadValidator.validate_mime_type(
            "document.pdf", b""
        )
        assert is_valid is False
        assert "signature" in error.lower()

    def test_too_short_content(self):
        """Rejects content shorter than magic bytes."""
        is_valid, error = FileUploadValidator.validate_mime_type(
            "document.pdf", b"%PD"
        )
        assert is_valid is False
        assert "signature" in error.lower()


class TestValidatePdfStructure:
    """Tests for FileUploadValidator.validate_pdf_structure()."""

    def test_valid_pdf_structure(self, mock_pdf_content):
        """Accepts valid PDF structure."""
        with patch(
            "src.local_deep_research.security.file_upload_validator.pdfplumber"
        ) as mock_pdfplumber:
            mock_pdf = Mock()
            mock_page = Mock()
            mock_page.width = 612
            mock_page.height = 792
            mock_pdf.pages = [mock_page]
            mock_pdfplumber.open.return_value.__enter__ = Mock(
                return_value=mock_pdf
            )
            mock_pdfplumber.open.return_value.__exit__ = Mock(
                return_value=False
            )

            is_valid, error = FileUploadValidator.validate_pdf_structure(
                "document.pdf", mock_pdf_content
            )
            assert is_valid is True
            assert error is None

    def test_pdf_no_pages(self, mock_pdf_content):
        """Rejects PDF with no pages."""
        with patch(
            "src.local_deep_research.security.file_upload_validator.pdfplumber"
        ) as mock_pdfplumber:
            mock_pdf = Mock()
            mock_pdf.pages = []
            mock_pdfplumber.open.return_value.__enter__ = Mock(
                return_value=mock_pdf
            )
            mock_pdfplumber.open.return_value.__exit__ = Mock(
                return_value=False
            )

            is_valid, error = FileUploadValidator.validate_pdf_structure(
                "document.pdf", mock_pdf_content
            )
            assert is_valid is False
            assert "no pages" in error.lower()

    def test_pdf_parse_error(self, mock_pdf_content):
        """Rejects PDF that cannot be parsed."""
        with patch(
            "src.local_deep_research.security.file_upload_validator.pdfplumber"
        ) as mock_pdfplumber:
            mock_pdfplumber.open.side_effect = Exception("PDF parsing error")

            is_valid, error = FileUploadValidator.validate_pdf_structure(
                "corrupt.pdf", mock_pdf_content
            )
            assert is_valid is False
            assert "invalid" in error.lower() or "corrupted" in error.lower()

    def test_pdf_none_pages(self, mock_pdf_content):
        """Rejects PDF with None pages."""
        with patch(
            "src.local_deep_research.security.file_upload_validator.pdfplumber"
        ) as mock_pdfplumber:
            mock_pdf = Mock()
            mock_pdf.pages = None
            mock_pdfplumber.open.return_value.__enter__ = Mock(
                return_value=mock_pdf
            )
            mock_pdfplumber.open.return_value.__exit__ = Mock(
                return_value=False
            )

            is_valid, error = FileUploadValidator.validate_pdf_structure(
                "document.pdf", mock_pdf_content
            )
            assert is_valid is False
            assert "no pages" in error.lower()


class TestValidateUpload:
    """Tests for FileUploadValidator.validate_upload()."""

    def test_valid_upload(self, mock_pdf_content):
        """Accepts valid file upload."""
        with patch(
            "src.local_deep_research.security.file_upload_validator.pdfplumber"
        ) as mock_pdfplumber:
            mock_pdf = Mock()
            mock_page = Mock()
            mock_page.width = 612
            mock_page.height = 792
            mock_pdf.pages = [mock_page]
            mock_pdfplumber.open.return_value.__enter__ = Mock(
                return_value=mock_pdf
            )
            mock_pdfplumber.open.return_value.__exit__ = Mock(
                return_value=False
            )

            is_valid, error = FileUploadValidator.validate_upload(
                "document.pdf", mock_pdf_content
            )
            assert is_valid is True
            assert error is None

    def test_upload_file_too_large(self):
        """Rejects upload that's too large."""
        large_content = b"%PDF-1.4" + b"x" * FileUploadValidator.MAX_FILE_SIZE
        is_valid, error = FileUploadValidator.validate_upload(
            "document.pdf", large_content
        )
        assert is_valid is False
        assert "too large" in error.lower()

    def test_upload_wrong_extension(self, mock_pdf_content):
        """Rejects upload with wrong extension."""
        is_valid, error = FileUploadValidator.validate_upload(
            "document.txt", mock_pdf_content
        )
        assert is_valid is False
        assert "only pdf" in error.lower()

    def test_upload_wrong_magic_bytes(self):
        """Rejects upload with wrong magic bytes."""
        content = b"not a pdf"
        is_valid, error = FileUploadValidator.validate_upload(
            "document.pdf", content
        )
        assert is_valid is False
        assert "signature" in error.lower()

    def test_upload_corrupted_pdf(self, mock_pdf_content):
        """Rejects corrupted PDF."""
        with patch(
            "src.local_deep_research.security.file_upload_validator.pdfplumber"
        ) as mock_pdfplumber:
            mock_pdfplumber.open.side_effect = Exception("Corrupted")

            is_valid, error = FileUploadValidator.validate_upload(
                "document.pdf", mock_pdf_content
            )
            assert is_valid is False
            assert "invalid" in error.lower() or "corrupted" in error.lower()

    def test_upload_with_content_length(self, mock_pdf_content):
        """Validates with content length header."""
        with patch(
            "src.local_deep_research.security.file_upload_validator.pdfplumber"
        ) as mock_pdfplumber:
            mock_pdf = Mock()
            mock_page = Mock()
            mock_page.width = 612
            mock_page.height = 792
            mock_pdf.pages = [mock_page]
            mock_pdfplumber.open.return_value.__enter__ = Mock(
                return_value=mock_pdf
            )
            mock_pdfplumber.open.return_value.__exit__ = Mock(
                return_value=False
            )

            is_valid, error = FileUploadValidator.validate_upload(
                "document.pdf",
                mock_pdf_content,
                content_length=len(mock_pdf_content),
            )
            assert is_valid is True
            assert error is None

    def test_upload_content_length_mismatch_large(self, mock_pdf_content):
        """Rejects when content length indicates too large."""
        # Content length header says file is too large
        is_valid, error = FileUploadValidator.validate_upload(
            "document.pdf",
            mock_pdf_content,
            content_length=FileUploadValidator.MAX_FILE_SIZE + 1,
        )
        assert is_valid is False
        assert "too large" in error.lower()


class TestSecurityScenarios:
    """Integration tests for security scenarios."""

    def test_disguised_executable(self):
        """Rejects executable disguised as PDF."""
        # Windows executable magic bytes
        content = b"MZ" + b"\x00" * 100
        is_valid, error = FileUploadValidator.validate_upload(
            "malware.pdf", content
        )
        assert is_valid is False

    def test_zip_bomb_prevention(self):
        """Rejects suspiciously large claimed size."""
        # Content length claims enormous size
        content = b"%PDF-1.4 small content"
        is_valid, error = FileUploadValidator.validate_upload(
            "bomb.pdf",
            content,
            content_length=10 * 1024 * 1024 * 1024,  # 10GB
        )
        assert is_valid is False

    def test_polyglot_file_rejection(self):
        """Rejects files that start with valid PDF but have wrong extension."""
        content = b"%PDF-1.4 this is actually executable code"
        is_valid, error = FileUploadValidator.validate_upload(
            "script.js", content
        )
        assert is_valid is False

    def test_html_in_pdf_extension(self):
        """Rejects HTML file with PDF extension."""
        content = b"<html><script>alert('xss')</script></html>"
        is_valid, error = FileUploadValidator.validate_upload(
            "attack.pdf", content
        )
        assert is_valid is False
        assert "signature" in error.lower()
