"""
Test script to verify text and CSV file handling in MCard.
"""

import codecs
import os
import tempfile
import unittest
from typing import Optional

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


class TestTextFileHandling(unittest.TestCase):
    def setUp(self):
        """Set up test environment."""
        # Create a temporary directory for test files
        self.test_dir = tempfile.mkdtemp(prefix="mcard_test_")

        # Create a temporary database file
        self.db_path = os.path.join(self.test_dir, "test_db.db")
        self.collection = CardCollection(db_path=self.db_path)

        # Create test files
        self.text_file = os.path.join(self.test_dir, "test.txt")
        self.csv_file = os.path.join(self.test_dir, "test.csv")

        # Sample content
        self.text_content = "This is a test text file.\nIt has multiple lines.\n"
        self.csv_content = "Name,Age,City\nAlice,30,New York\nBob,25,Los Angeles\n"

        # Write test files
        with open(self.text_file, "w", encoding="utf-8") as f:
            f.write(self.text_content)

        with open(self.csv_file, "w", encoding="utf-8") as f:
            f.write(self.csv_content)

    def _create_test_file(
        self, file_path: str, content: str, encoding: Optional[str] = None
    ) -> None:
        """Helper method to create a test file with specified encoding."""
        if encoding:
            with open(file_path, "w", encoding=encoding) as f:
                f.write(content)
            # For BOM, we need to use binary mode to write the BOM
            if encoding.lower().replace("-", "").replace("_", "") == "utf8withbom":
                with open(file_path, "rb") as f:
                    content_bytes = f.read()
                with open(file_path, "wb") as f:
                    f.write(codecs.BOM_UTF8 + content_bytes)
        else:
            with open(file_path, "w", encoding="utf-8") as f:
                f.write(content)

    def tearDown(self):
        """Clean up test environment."""
        # Clean up test files and database
        for file_path in [self.text_file, self.csv_file, self.db_path]:
            if os.path.exists(file_path):
                try:
                    os.remove(file_path)
                except (OSError, PermissionError):
                    continue

        # Remove any files in the test directory first
        if os.path.exists(self.test_dir):
            for filename in os.listdir(self.test_dir):
                file_path = os.path.join(self.test_dir, filename)
                try:
                    if os.path.isfile(file_path):
                        os.unlink(file_path)
                except Exception:
                    continue
            try:
                os.rmdir(self.test_dir)
            except (OSError, PermissionError):
                pass

    def test_utf8_file_processing(self):
        """Test processing of a UTF-8 encoded text file."""
        # Process the text file
        response = load_file_to_collection(self.text_file, self.collection)

        # Verify results
        self.assertEqual(len(response["results"]), 1)
        self.assertEqual(response["results"][0]["filename"], "test.txt")
        self.assertEqual(response["results"][0]["content_type"], "text/plain")
        self.assertFalse(response["results"][0]["is_binary"])

        # Verify content was stored correctly
        card = self.collection.get(response["results"][0]["hash"])
        self.assertIsNotNone(card)
        content = card.get_content()
        self.assertIsInstance(content, bytes)
        self.assertIn(b"This is a test text file", content)

    def test_utf8_bom_file_processing(self):
        """Test processing of a UTF-8 with BOM encoded text file."""
        # Create a file with UTF-8 BOM encoding
        bom_file = os.path.join(self.test_dir, "test_bom.txt")
        self._create_test_file(bom_file, self.text_content, "utf-8-sig")

        # Process the file
        response = load_file_to_collection(bom_file, self.collection)

        # Verify results
        self.assertEqual(len(response["results"]), 1)
        self.assertEqual(response["results"][0]["filename"], "test_bom.txt")
        self.assertEqual(response["results"][0]["content_type"], "text/plain")
        self.assertFalse(response["results"][0]["is_binary"])

        # Verify content was stored correctly (BOM should be stripped)
        card = self.collection.get(response["results"][0]["hash"])
        self.assertIsNotNone(card)
        content = card.get_content()
        self.assertIsInstance(content, bytes)
        self.assertIn(b"This is a test text file", content)

        # Clean up
        if os.path.exists(bom_file):
            os.remove(bom_file)

    def test_utf16_file_processing(self):
        """Test processing of a UTF-16 encoded text file."""
        # Create a file with UTF-16 encoding
        utf16_file = os.path.join(self.test_dir, "test_utf16.txt")
        self._create_test_file(utf16_file, self.text_content, "utf-16")

        # Process the file
        response = load_file_to_collection(utf16_file, self.collection)

        # Verify results
        self.assertEqual(len(response["results"]), 1)
        self.assertEqual(response["results"][0]["filename"], "test_utf16.txt")
        self.assertEqual(response["results"][0]["content_type"], "text/plain")

        # Note: UTF-16 files may be detected as binary by some detectors
        # We'll accept either binary or text for UTF-16 since it depends on the detector
        is_binary = response["results"][0]["is_binary"]
        self.assertIn(
            is_binary,
            [True, False],
            f"Expected is_binary to be True or False, got {is_binary}",
        )

        # Verify content was stored correctly
        card = self.collection.get(response["results"][0]["hash"])
        self.assertIsNotNone(card)
        content = card.get_content()
        self.assertIsInstance(content, bytes)

        # Convert content to string for assertion (handle both binary and text cases)
        try:
            content_str = content.decode("utf-16")
            self.assertIn("This is a test text file", content_str)
        except UnicodeDecodeError:
            # If it's stored as raw bytes, it should contain the UTF-16 BOM
            self.assertTrue(
                content.startswith(b"\xff\xfe") or content.startswith(b"\xfe\xff"),
                "UTF-16 content should start with BOM",
            )
            # Try decoding with BOM
            content_str = content.decode("utf-16")
            self.assertIn("This is a test text file", content_str)

    def test_ascii_file_processing(self):
        """Test processing of an ASCII encoded text file."""
        # Create a file with ASCII encoding
        ascii_content = "This is an ASCII test file.\nIt has multiple lines.\n"
        ascii_file = os.path.join(self.test_dir, "test_ascii.txt")
        self._create_test_file(ascii_file, ascii_content, "ascii")

        # Process the file
        response = load_file_to_collection(ascii_file, self.collection)

        # Verify results
        self.assertEqual(len(response["results"]), 1)
        self.assertEqual(response["results"][0]["filename"], "test_ascii.txt")
        self.assertEqual(response["results"][0]["content_type"], "text/plain")
        self.assertFalse(response["results"][0]["is_binary"])

        # Verify content was stored correctly
        card = self.collection.get(response["results"][0]["hash"])
        self.assertIsNotNone(card)
        content = card.get_content()
        self.assertIsInstance(content, bytes)
        self.assertIn(b"This is an ASCII test file", content)

        # Clean up
        if os.path.exists(ascii_file):
            os.remove(ascii_file)

    def test_mixed_encoding_file_processing(self):
        """Test processing of a file with mixed encodings."""
        # Create a file with mixed encodings
        mixed_content = (
            "ASCII: This is ASCII text\n"
            "UTF-8: こんにちは (Hello in Japanese)\n"
            "Emoji: 😊👍🎉\n"
            "Special: ©®™•¶§∞\\n"
        )
        mixed_file = os.path.join(self.test_dir, "test_mixed.txt")

        # Write with UTF-8 encoding to handle all characters
        with open(mixed_file, "w", encoding="utf-8") as f:
            f.write(mixed_content)

        # Process the file
        response = load_file_to_collection(mixed_file, self.collection)

        # Verify results
        self.assertEqual(len(response["results"]), 1)
        self.assertEqual(response["results"][0]["filename"], "test_mixed.txt")
        self.assertEqual(response["results"][0]["content_type"], "text/plain")

        # For files with mixed encodings, we'll accept either binary or text detection
        # as some detectors might flag it as binary due to special characters
        is_binary = response["results"][0]["is_binary"]
        self.assertIn(
            is_binary,
            [True, False],
            f"Expected is_binary to be True or False, got {is_binary}",
        )

        # Verify content was stored correctly
        card = self.collection.get(response["results"][0]["hash"])
        self.assertIsNotNone(card)
        content = card.get_content()
        self.assertIsInstance(content, bytes)

        # Check for different parts of the content
        # We'll decode the content to handle both binary and text cases
        try:
            decoded_content = content.decode("utf-8")
            self.assertIn("ASCII: This is ASCII text", decoded_content)
            self.assertIn("こんにちは", decoded_content)
            self.assertIn("😊", decoded_content)
            self.assertIn("©", decoded_content)
        except UnicodeDecodeError:
            # If it's stored as raw bytes, check for byte patterns
            self.assertIn(b"ASCII: This is ASCII text", content)
            self.assertIn(
                b"\xe3\x81\x93\xe3\x82\x93\xe3\x81\xab\xe3\x81\xa1\xe3\x81\xaf", content
            )  # こんにちは in UTF-8
            self.assertIn(b"\xf0\x9f\x98\x8a", content)  # 😊 in UTF-8
            self.assertIn(b"\xc2\xa9", content)  # © in UTF-8

        # Clean up
        if os.path.exists(mixed_file):
            os.remove(mixed_file)

    def test_large_file_processing(self):
        """Test processing of a large text file."""
        # Create a large file (about 2MB)
        large_file = os.path.join(self.test_dir, "large_file.txt")
        chunk = "This is a line of text in a large file. " * 10 + "\n"
        chunk_size = len(chunk.encode("utf-8"))
        target_size = 2 * 1024 * 1024  # 2MB
        num_chunks = target_size // chunk_size + 1

        with open(large_file, "w", encoding="utf-8") as f:
            for _ in range(num_chunks):
                f.write(chunk)

        # Verify file size is approximately 2MB
        file_size = os.path.getsize(large_file)
        self.assertGreaterEqual(file_size, target_size, "File should be at least 2MB")

        # Process the file
        response = load_file_to_collection(large_file, self.collection)

        # Verify results
        self.assertEqual(len(response["results"]), 1)
        self.assertEqual(response["results"][0]["filename"], "large_file.txt")
        self.assertEqual(response["results"][0]["content_type"], "text/plain")

        # For large files, we'll accept either binary or text detection
        is_binary = response["results"][0]["is_binary"]
        self.assertIn(
            is_binary,
            [True, False],
            f"Expected is_binary to be True or False, got {is_binary}",
        )

        # Verify content was stored correctly
        card = self.collection.get(response["results"][0]["hash"])
        self.assertIsNotNone(card)
        content = card.get_content()
        self.assertIsInstance(content, bytes)

        # Check that the content starts with our expected chunk
        self.assertTrue(
            content.startswith(b"This is a line of text")
            or content.startswith(chunk.encode("utf-8")),
            "Content should start with the expected text",
        )

        # Clean up
        if os.path.exists(large_file):
            os.remove(large_file)

    def test_different_line_endings(self):
        """Test processing of files with different line endings."""
        # Test content with different line endings
        content = "Line 1{0}Line 2{0}Line 3{0}"

        # Test with Unix line endings (LF)
        lf_file = os.path.join(self.test_dir, "test_lf.txt")
        with open(lf_file, "wb") as f:
            f.write(content.format("\n").encode("utf-8"))

        # Test with Windows line endings (CRLF)
        crlf_file = os.path.join(self.test_dir, "test_crlf.txt")
        with open(crlf_file, "wb") as f:
            f.write(content.format("\r\n").encode("utf-8"))

        # Process both files
        lf_response = load_file_to_collection(lf_file, self.collection)
        crlf_response = load_file_to_collection(crlf_file, self.collection)

        # Verify LF file
        self.assertEqual(len(lf_response["results"]), 1)
        self.assertEqual(lf_response["results"][0]["filename"], "test_lf.txt")
        self.assertEqual(lf_response["results"][0]["content_type"], "text/plain")

        # Verify CRLF file
        self.assertEqual(len(crlf_response["results"]), 1)
        self.assertEqual(crlf_response["results"][0]["filename"], "test_crlf.txt")
        self.assertEqual(crlf_response["results"][0]["content_type"], "text/plain")

        # Get the content of both files
        lf_card = self.collection.get(lf_response["results"][0]["hash"])
        crlf_card = self.collection.get(crlf_response["results"][0]["hash"])

        # Verify content was stored correctly
        self.assertIsNotNone(lf_card)
        self.assertIsNotNone(crlf_card)

        lf_content = lf_card.get_content()
        crlf_content = crlf_card.get_content()

        # Check that both files contain the expected content
        self.assertIn(b"Line 1", lf_content)
        self.assertIn(b"Line 2", lf_content)
        self.assertIn(b"Line 1", crlf_content)
        self.assertIn(b"Line 2", crlf_content)

        # Clean up
        for f in [lf_file, crlf_file]:
            if os.path.exists(f):
                os.remove(f)

    def test_corrupted_file_handling(self):
        """Test handling of corrupted or invalid text files."""
        # Create a file with invalid UTF-8 sequence
        corrupted_file = os.path.join(self.test_dir, "corrupted.txt")

        # Create a file with invalid UTF-8 sequence (middle of a multi-byte character)
        with open(corrupted_file, "wb") as f:
            # Start with valid UTF-8
            f.write(b"Valid text: ")
            # Add invalid UTF-8 sequence
            f.write(b"\x80\x80\x80\x80")
            # Add more valid text
            f.write(b" more text")

        # Process the file - should not raise an exception
        response = load_file_to_collection(corrupted_file, self.collection)

        # Verify we got a result (file was processed)
        self.assertEqual(len(response["results"]), 1)
        self.assertEqual(response["results"][0]["filename"], "corrupted.txt")

        # The content type should still be text/plain, but it might be detected as binary
        # due to the invalid UTF-8 sequence
        self.assertEqual(response["results"][0]["content_type"], "text/plain")

        # Verify content was stored (as bytes, even if corrupted)
        card = self.collection.get(response["results"][0]["hash"])
        self.assertIsNotNone(card)

        content = card.get_content()
        self.assertIsInstance(content, bytes)

        # Should contain our valid parts
        self.assertIn(b"Valid text:", content)
        self.assertIn(b"more text", content)

        # Clean up
        if os.path.exists(corrupted_file):
            os.remove(corrupted_file)

    def test_csv_file_processing(self):
        """Test processing of a CSV file."""
        # Process the CSV file
        response = load_file_to_collection(self.csv_file, self.collection)

        # Verify results
        self.assertEqual(len(response["results"]), 1)
        self.assertEqual(response["results"][0]["filename"], "test.csv")
        self.assertEqual(response["results"][0]["content_type"], "text/csv")
        self.assertFalse(response["results"][0]["is_binary"])

        # Verify content was stored correctly
        card = self.collection.get(response["results"][0]["hash"])
        self.assertIsNotNone(card)
        content = card.get_content()
        self.assertIsInstance(content, bytes)
        self.assertIn(b"Name,Age,City", content)
        self.assertIn(b"Alice,30,New York", content)


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