"""
Test cases for handling problematic files with very long lines.

These tests verify that the MCard system can handle files with pathological
content patterns without hanging or crashing.
"""

import tempfile
import unittest
from pathlib import Path

from mcard import file_io
from mcard.loader import load_file_to_collection
from mcard.model.card_collection import CardCollection


class TestProblematicFiles(unittest.TestCase):
    """Test handling of files with problematic content patterns."""

    def setUp(self):
        """Set up test environment."""
        self.test_dir = tempfile.mkdtemp(prefix="mcard_problematic_test_")
        self.collection = CardCollection(db_path=":memory:")

        # Get the test data directory
        self.test_data_dir = Path(__file__).parent / "test_data"

    def tearDown(self):
        """Clean up test environment."""
        import shutil

        shutil.rmtree(self.test_dir, ignore_errors=True)

    def test_single_massive_line_94kb_file(self):
        """Test handling of a 94KB single-line file."""
        test_file = self.test_data_dir / "single_massive_line_94kb.txt"

        if not test_file.exists():
            self.skipTest(f"Test file not found: {test_file}")

        # Verify the file is detected as problematic (since it's .txt with long lines)
        is_problematic = file_io.is_problematic_file(test_file)
        self.assertTrue(
            is_problematic,
            "94KB .txt file with single massive line should be detected as problematic",
        )

        # Try to read the file - should raise an error due to single massive line
        with self.assertRaises((IOError, TimeoutError)) as context:
            file_io.read_file_safely(test_file)

        # Verify the error message indicates the issue
        error_msg = str(context.exception)
        self.assertTrue(
            "single massive line" in error_msg or "timeout" in error_msg,
            f"Expected timeout or massive line error, got: {error_msg}",
        )

    def test_single_massive_line_115kb_file(self):
        """Test handling of a 115KB single-line file."""
        test_file = self.test_data_dir / "single_massive_line_115kb.txt"

        if not test_file.exists():
            self.skipTest(f"Test file not found: {test_file}")

        # Try to read the file - should raise an error
        with self.assertRaises((IOError, TimeoutError)) as context:
            file_io.read_file_safely(test_file)

        error_msg = str(context.exception)
        self.assertTrue(
            "single massive line" in error_msg or "timeout" in error_msg,
            f"Expected timeout or massive line error, got: {error_msg}",
        )

    def test_single_massive_line_427kb_file(self):
        """Test handling of a 427KB single-line file."""
        test_file = self.test_data_dir / "single_massive_line_427kb.txt"

        if not test_file.exists():
            self.skipTest(f"Test file not found: {test_file}")

        # Try to read the file - should raise an error
        with self.assertRaises((IOError, TimeoutError)) as context:
            file_io.read_file_safely(test_file)

        error_msg = str(context.exception)
        self.assertTrue(
            "single massive line" in error_msg or "timeout" in error_msg,
            f"Expected timeout or massive line error, got: {error_msg}",
        )

    def test_load_file_to_collection_with_problematic_file(self):
        """Test that load_file_to_collection handles problematic files gracefully."""
        test_file = self.test_data_dir / "single_massive_line_94kb.txt"

        if not test_file.exists():
            self.skipTest(f"Test file not found: {test_file}")

        # This should not hang and should return empty results
        response = load_file_to_collection(test_file, self.collection)

        # Should return empty list since file processing failed
        self.assertEqual(
            len(response["results"]), 0, "Problematic file should return no results"
        )

    def test_file_screening_logic(self):
        """Test the file screening logic for different file types."""
        # Test that .txt files with long lines are caught
        test_file = self.test_data_dir / "single_massive_line_94kb.txt"

        if test_file.exists():
            # Since we renamed .js to .txt, it should be more restrictive
            is_problematic = file_io.is_problematic_file(test_file)
            # Should be detected as problematic since it's a .txt file with pathological content
            self.assertTrue(is_problematic, "File should be detected as problematic")

            # Test the actual reading behavior
            try:
                file_io.read_file_safely(test_file)
                self.fail(
                    "Should have raised an exception for single massive line file"
                )
            except (OSError, TimeoutError):
                # This is expected
                pass

    def test_timeout_mechanism(self):
        """Test that the timeout mechanism works for large files."""
        test_file = self.test_data_dir / "single_massive_line_427kb.txt"

        if not test_file.exists():
            self.skipTest(f"Test file not found: {test_file}")

        import time

        start_time = time.time()

        try:
            file_io.read_file_safely(test_file)
            self.fail("Should have raised a timeout or error")
        except (OSError, TimeoutError):
            elapsed_time = time.time() - start_time
            # Should timeout within reasonable time (much less than 30 seconds)
            self.assertLess(elapsed_time, 15, "Timeout should occur within 15 seconds")

    def test_file_size_detection(self):
        """Test that file size detection works correctly."""
        for filename in [
            "single_massive_line_94kb.txt",
            "single_massive_line_115kb.txt",
            "single_massive_line_427kb.txt",
        ]:
            test_file = self.test_data_dir / filename
            if test_file.exists():
                file_size = test_file.stat().st_size
                expected_size = (
                    int(filename.split("_")[-1].replace("kb.txt", "")) * 1024
                )
                # Allow some variance due to exact content
                self.assertGreater(
                    file_size,
                    expected_size * 0.8,
                    f"File {filename} should be approximately the expected size",
                )

    def test_generated_single_line_1_2mb_txt_is_problematic(self):
        """A generated 1.2MB single-line .txt should be flagged problematic and fail to read fully."""
        path = Path(self.test_dir) / "generated_single_line_1_2mb.txt"
        size_bytes = int(1.2 * 1024 * 1024)
        with open(path, "wb") as f:
            f.write(b"A" * size_bytes)

        # .txt with extremely long single line should be problematic
        self.assertTrue(file_io.is_problematic_file(path))
        with self.assertRaises((IOError, TimeoutError)):
            file_io.read_file_safely(path)

    def test_small_single_line_under_1kb_not_problematic(self):
        """Files <= 1KB should be considered safe by _is_problematic_file."""
        path = Path(self.test_dir) / "small_single_line_900b.txt"
        with open(path, "wb") as f:
            f.write(b"B" * 900)

        self.assertFalse(file_io.is_problematic_file(path))

    def test_huge_file_over_10mb_flagged_by_size(self):
        """Files > 10MB must be flagged without content scanning."""
        path = Path(self.test_dir) / "huge_over_10mb.bin"
        with open(path, "wb") as f:
            f.truncate(10 * 1024 * 1024 + 1)

        self.assertTrue(file_io.is_problematic_file(path))

    def test_known_long_line_js_under_1mb_is_permissive(self):
        """Known long-line types (e.g., .js) under 1MB should be more permissive and not auto-flagged."""
        path = Path(self.test_dir) / "minified_like_800kb.js"
        size_bytes = 800 * 1024
        # Create content that is mostly one line but with periodic newlines to avoid
        # the strict single-massive-line pathology while remaining under 1MB.
        header = b"function a(){return 'x';}//"
        remaining = size_bytes - len(header)
        chunk = b"C" * 8191 + b"\n"  # 8KB-ish with newline
        body = bytearray()
        while remaining > 0:
            take = min(len(chunk), remaining)
            body.extend(chunk[:take])
            remaining -= take
        with open(path, "wb") as f:
            f.write(header + bytes(body))

        # Expect not problematic due to known extension and size under 1MB limit
        self.assertFalse(file_io.is_problematic_file(path))

    def test_txt_with_200kb_single_line_is_problematic(self):
        """A .txt with a 200KB single line should be flagged problematic by pathological-line check."""
        path = Path(self.test_dir) / "single_line_200kb.txt"
        with open(path, "wb") as f:
            f.write(b"D" * (200 * 1024))

        self.assertTrue(file_io.is_problematic_file(path))
        with self.assertRaises((IOError, TimeoutError)):
            file_io.read_file_safely(path)


if __name__ == "__main__":
    unittest.main()
