import os
import shutil
import unittest
import tempfile

from src.compressor import Compressor, CompressionLevel


def create_dummy_file(content: str = "Hello, World!" * 100, file_name: str = "test.txt"):
    """
    Create a dummy file with the specified content for testing.
    """
    temp_dir = tempfile.mkdtemp()
    file_path = os.path.join(temp_dir, file_name)
    with open(file_path, 'w') as f:
        f.write(content)
    return file_path, temp_dir


def cleanup_temp_dir(temp_dir):
    """
    Remove the temporary directory and all its contents.
    """
    shutil.rmtree(temp_dir)


class BaseCompressorTest(unittest.TestCase):
    """
    Base test class for setting up and tearing down common test files.
    """

    def setUp(self):
        self.input_file, self.temp_dir = create_dummy_file()
        self.output_file = os.path.join(self.temp_dir, "compressed.zst")
        self.decompressed_file = os.path.join(self.temp_dir, "decompressed.txt")

    def tearDown(self):
        cleanup_temp_dir(self.temp_dir)


class TestCompression(BaseCompressorTest):
    """
    Tests for compression functionality.
    """

    def test_compress_file(self):
        compressor = Compressor(level=CompressionLevel.normal)
        compressor.compress_file(self.input_file, self.output_file)
        self.assertTrue(os.path.exists(self.output_file), "Compressed file does not exist.")


class TestDecompression(BaseCompressorTest):
    """
    Tests for decompression functionality.
    """

    def test_decompress_file(self):
        compressor = Compressor(level=CompressionLevel.normal)
        compressor.compress_file(self.input_file, self.output_file)
        compressor.decompress_file(self.output_file, self.decompressed_file)

        with open(self.input_file, 'r') as original, open(self.decompressed_file, 'r') as decompressed:
            self.assertEqual(original.read(), decompressed.read(), "Decompressed content does not match the original.")


class TestCompressionLevels(BaseCompressorTest):
    """
    Tests for different compression levels.
    """

    def test_compression_level_effect(self):
        output_file_level_fastest = os.path.join(self.temp_dir, "compressed_level_fastest.zst")
        output_file_level_best = os.path.join(self.temp_dir, "compressed_level_best.zst")

        compressor_level_fastest = Compressor(level=CompressionLevel.fastest)
        compressor_level_best = Compressor(level=CompressionLevel.best)

        compressor_level_fastest.compress_file(self.input_file, output_file_level_fastest)
        compressor_level_best.compress_file(self.input_file, output_file_level_best)

        size_level_fastest = os.path.getsize(output_file_level_fastest)
        size_level_best = os.path.getsize(output_file_level_best)

        self.assertLess(
            size_level_best, size_level_fastest,
            "Higher compression level should result in a smaller file."
        )


class TestEdgeCases(unittest.TestCase):
    """
    Tests for edge cases such as empty files and non-existing files.
    """

    def test_empty_file(self):
        input_file, temp_dir = create_dummy_file(content="")
        output_file = os.path.join(temp_dir, "compressed_empty.zst")
        decompressed_file = os.path.join(temp_dir, "decompressed_empty.txt")

        compressor = Compressor(level=CompressionLevel.normal)
        compressor.compress_file(input_file, output_file)
        compressor.decompress_file(output_file, decompressed_file)

        with open(input_file, 'r') as original, open(decompressed_file, 'r') as decompressed:
            self.assertEqual(original.read(), decompressed.read(), "Decompressed empty file does not match original.")

        cleanup_temp_dir(temp_dir)

    def test_file_not_found(self):
        compressor = Compressor(level=CompressionLevel.normal)
        with self.assertRaises(FileNotFoundError):
            compressor.compress_file("non_existing_file.txt", "output.zst")

    def test_permission_error(self):
        input_file, temp_dir = create_dummy_file()
        output_file = os.path.join(temp_dir, "compressed.zst")

        os.chmod(temp_dir, 0o400)  # Read-only permissions
        compressor = Compressor(level=CompressionLevel.normal)

        with self.assertRaises(PermissionError):
            compressor.compress_file(input_file, output_file)

        os.chmod(temp_dir, 0o700)  # Restore permissions
        cleanup_temp_dir(temp_dir)
