"""Tests for file validation utility."""
import io
from unittest.mock import patch, MagicMock
from app.utils.file_validator import validate_file, FileValidationError
import pytest


class TestFileValidator:
    def test_no_file_raises(self, app):
        """Should raise when no file provided."""
        with app.app_context():
            with pytest.raises(FileValidationError, match="No file provided"):
                validate_file(None, allowed_types=["pdf"])

    def test_empty_filename_raises(self, app):
        """Should raise when filename is empty."""
        with app.app_context():
            mock_file = MagicMock()
            mock_file.filename = ''
            with pytest.raises(FileValidationError, match="No file provided"):
                validate_file(mock_file, allowed_types=["pdf"])

    def test_wrong_extension_raises(self, app):
        """Should raise when file extension is not allowed."""
        with app.app_context():
            mock_file = MagicMock()
            mock_file.filename = 'test.exe'
            with pytest.raises(FileValidationError, match="not allowed"):
                validate_file(mock_file, allowed_types=["pdf"])

    def test_empty_file_raises(self, app):
        """Should raise when file is empty (0 bytes)."""
        with app.app_context():
            content = io.BytesIO(b'')
            mock_file = MagicMock()
            mock_file.filename = 'test.pdf'
            mock_file.seek = content.seek
            mock_file.tell = content.tell
            mock_file.read = content.read
            with pytest.raises(FileValidationError, match="empty"):
                validate_file(mock_file, allowed_types=["pdf"])

    def test_valid_pdf_passes(self, app):
        """Should accept valid PDF file with correct magic bytes."""
        with app.app_context():
            pdf_bytes = b'%PDF-1.4 test content' + b'\x00' * 8192
            content = io.BytesIO(pdf_bytes)

            mock_file = MagicMock()
            mock_file.filename = 'document.pdf'
            mock_file.seek = content.seek
            mock_file.tell = content.tell
            mock_file.read = content.read

            with patch('app.utils.file_validator.HAS_MAGIC', True), patch(
                'app.utils.file_validator.magic', create=True
            ) as mock_magic:
                mock_magic.from_buffer.return_value = 'application/pdf'
                filename, ext = validate_file(mock_file, allowed_types=["pdf"])

            assert filename == 'document.pdf'
            assert ext == 'pdf'

    def test_valid_html_passes(self, app):
        """Should accept valid HTML file with correct MIME type."""
        with app.app_context():
            html_bytes = b'<!doctype html><html><body>Hello</body></html>'
            content = io.BytesIO(html_bytes)

            mock_file = MagicMock()
            mock_file.filename = 'page.html'
            mock_file.seek = content.seek
            mock_file.tell = content.tell
            mock_file.read = content.read

            with patch('app.utils.file_validator.HAS_MAGIC', True), patch(
                'app.utils.file_validator.magic', create=True
            ) as mock_magic:
                mock_magic.from_buffer.return_value = 'text/html'
                filename, ext = validate_file(mock_file, allowed_types=["html", "htm"])

            assert filename == 'page.html'
            assert ext == 'html'

    def test_mime_mismatch_raises(self, app):
        """Should raise when MIME type doesn't match extension."""
        with app.app_context():
            content = io.BytesIO(b'not a real pdf' + b'\x00' * 8192)

            mock_file = MagicMock()
            mock_file.filename = 'fake.pdf'
            mock_file.seek = content.seek
            mock_file.tell = content.tell
            mock_file.read = content.read

            with patch('app.utils.file_validator.HAS_MAGIC', True), patch(
                'app.utils.file_validator.magic', create=True
            ) as mock_magic:
                mock_magic.from_buffer.return_value = 'text/plain'
                with pytest.raises(FileValidationError, match="does not match"):
                    validate_file(mock_file, allowed_types=["pdf"])

    def test_file_too_large_raises(self, app):
        """Should raise when file exceeds size limit."""
        with app.app_context():
            # Create a file larger than the PDF size limit (20MB)
            large_content = io.BytesIO(b'%PDF-1.4' + b'\x00' * (21 * 1024 * 1024))

            mock_file = MagicMock()
            mock_file.filename = 'large.pdf'
            mock_file.seek = large_content.seek
            mock_file.tell = large_content.tell
            mock_file.read = large_content.read

            with pytest.raises(FileValidationError, match="too large"):
                validate_file(mock_file, allowed_types=["pdf"])

    def test_dangerous_pdf_raises(self, app):
        """Should raise when PDF contains dangerous patterns."""
        with app.app_context():
            pdf_bytes = b'%PDF-1.4 /JavaScript evil_code' + b'\x00' * 8192
            content = io.BytesIO(pdf_bytes)

            mock_file = MagicMock()
            mock_file.filename = 'evil.pdf'
            mock_file.seek = content.seek
            mock_file.tell = content.tell
            mock_file.read = content.read

            with patch('app.utils.file_validator.HAS_MAGIC', True), patch(
                'app.utils.file_validator.magic', create=True
            ) as mock_magic:
                mock_magic.from_buffer.return_value = 'application/pdf'
                with pytest.raises(FileValidationError, match="unsafe"):
                    validate_file(mock_file, allowed_types=["pdf"])
