from __future__ import annotations

import tempfile
from pathlib import Path

import pytest
from pytestqt.qtbot import QtBot

from ..cli import (
    ChecksumCalculator,
    ChecksumConfig,
    ChecksumDialog,
    HashAlgorithm,
    gui_main,
    main,
    parse_cli_arguments,
    run_cli_mode,
)


class TestHashAlgorithm:
    """Tests for HashAlgorithm enumeration."""

    def test_supported_algorithms(self) -> None:
        """Test that all expected algorithms are supported."""
        expected_algorithms = {
            HashAlgorithm.MD5,
            HashAlgorithm.SHA1,
            HashAlgorithm.SHA256,
            HashAlgorithm.SHA384,
            HashAlgorithm.SHA512,
            HashAlgorithm.BLAKE2B,
            HashAlgorithm.BLAKE2S,
        }
        assert set(HashAlgorithm) == expected_algorithms

    def test_algorithm_values(self) -> None:
        """Test that algorithm values match hashlib function names."""
        assert HashAlgorithm.MD5.value == "md5"
        assert HashAlgorithm.SHA1.value == "sha1"
        assert HashAlgorithm.SHA256.value == "sha256"
        assert HashAlgorithm.SHA384.value == "sha384"
        assert HashAlgorithm.SHA512.value == "sha512"
        assert HashAlgorithm.BLAKE2B.value == "blake2b"
        assert HashAlgorithm.BLAKE2S.value == "blake2s"


class TestChecksumConfig:
    """Tests for ChecksumConfig dataclass."""

    def test_default_configuration(self) -> None:
        """Test default configuration values."""
        config = ChecksumConfig()
        assert config.default_algorithm == HashAlgorithm.MD5
        assert config.enable_comparison is False
        assert config.output_format == "hex"

    def test_custom_configuration(self) -> None:
        """Test custom configuration values."""
        config = ChecksumConfig(
            default_algorithm=HashAlgorithm.SHA256,
            enable_comparison=True,
            output_format="hex",
        )
        assert config.default_algorithm == HashAlgorithm.SHA256
        assert config.enable_comparison is True
        assert config.output_format == "hex"


class TestChecksumCalculator:
    """Tests for ChecksumCalculator core functionality."""

    @pytest.fixture
    def calculator(self) -> ChecksumCalculator:
        """Provide a default calculator instance."""
        return ChecksumCalculator()

    def test_initialization_with_default_config(self, calculator: ChecksumCalculator) -> None:
        """Test calculator initialization with default configuration."""
        assert calculator.current_algorithm == HashAlgorithm.MD5

    def test_initialization_with_custom_config(self) -> None:
        """Test calculator initialization with custom configuration."""
        config = ChecksumConfig(default_algorithm=HashAlgorithm.SHA256)
        calculator = ChecksumCalculator(config)
        assert calculator.current_algorithm == HashAlgorithm.SHA256

    def test_set_algorithm_valid(self, calculator: ChecksumCalculator) -> None:
        """Test setting valid hash algorithm."""
        calculator.set_algorithm(HashAlgorithm.SHA256)
        assert calculator.current_algorithm == HashAlgorithm.SHA256

    def test_set_algorithm_invalid(self, calculator: ChecksumCalculator) -> None:
        """Test setting invalid hash algorithm raises ValueError."""
        with pytest.raises(ValueError, match="Unsupported algorithm"):
            # Pass a string that's not a valid HashAlgorithm member
            calculator.set_algorithm("INVALID_ALGORITHM")  # type: ignore

    @pytest.mark.parametrize(
        ("algorithm", "content", "expected_hash"),
        [
            (HashAlgorithm.MD5, "hello world", "5eb63bbbe01eeed093cb22bb8f5acdc3"),
            (
                HashAlgorithm.SHA1,
                "hello world",
                "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed",
            ),
            (
                HashAlgorithm.SHA256,
                "hello world",
                "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
            ),
        ],
    )
    def test_calculate_string_checksum(
        self,
        calculator: ChecksumCalculator,
        algorithm: HashAlgorithm,
        content: str,
        expected_hash: str,
    ) -> None:
        """Test string checksum calculation for different algorithms."""
        calculator.set_algorithm(algorithm)
        result = calculator.calculate_string_checksum(content)
        assert result == expected_hash

    def test_calculate_string_checksum_empty_content(self, calculator: ChecksumCalculator) -> None:
        """Test string checksum calculation with empty content raises ValueError."""
        with pytest.raises(ValueError, match="Content cannot be empty"):
            calculator.calculate_string_checksum("")

    def test_calculate_file_checksum(self, calculator: ChecksumCalculator) -> None:
        """Test file checksum calculation."""
        # Create temporary file with known content
        with tempfile.NamedTemporaryFile(mode="w", delete=False, encoding="utf-8") as f:
            f.write("hello world")
            temp_file_path = Path(f.name)

        try:
            # Test with MD5
            calculator.set_algorithm(HashAlgorithm.MD5)
            result = calculator.calculate_file_checksum(temp_file_path)
            expected = "5eb63bbbe01eeed093cb22bb8f5acdc3"
            assert result == expected

            # Test with SHA256
            calculator.set_algorithm(HashAlgorithm.SHA256)
            result = calculator.calculate_file_checksum(temp_file_path)
            expected = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
            assert result == expected
        finally:
            temp_file_path.unlink()

    def test_calculate_file_checksum_nonexistent_file(self, calculator: ChecksumCalculator) -> None:
        """Test file checksum calculation with nonexistent file raises FileNotFoundError."""
        with pytest.raises(FileNotFoundError):
            calculator.calculate_file_checksum(Path("/nonexistent/file.txt"))

    def test_verify_checksum_exact_match(self, calculator: ChecksumCalculator) -> None:
        """Test checksum verification with exact match."""
        calculated = "5eb63bbbe01eeed093cb22bb8f5acdc3"
        expected = "5eb63bbbe01eeed093cb22bb8f5acdc3"
        assert calculator.verify_checksum(calculated, expected) is True

    def test_verify_checksum_case_insensitive_match(self, calculator: ChecksumCalculator) -> None:
        """Test checksum verification is case insensitive."""
        calculated = "5EB63BBBE01EEED093CB22BB8F5ACDC3"
        expected = "5eb63bbbe01eeed093cb22bb8f5acdc3"
        assert calculator.verify_checksum(calculated, expected) is True

    def test_verify_checksum_mismatch(self, calculator: ChecksumCalculator) -> None:
        """Test checksum verification with mismatch."""
        calculated = "5eb63bbbe01eeed093cb22bb8f5acdc3"
        expected = "different_hash_value"
        assert calculator.verify_checksum(calculated, expected) is False


class TestChecksumDialog:
    """Tests for ChecksumDialog GUI component."""

    def test_dialog_initialization(self, qtbot: QtBot) -> None:
        """Test dialog initialization."""
        dialog = ChecksumDialog()
        qtbot.addWidget(dialog)

        # Verify dialog is created successfully
        assert dialog is not None
        assert hasattr(dialog, "calculator")
        assert hasattr(dialog, "algorithm_buttons")
        assert len(dialog.algorithm_buttons) == 7  # 7 algorithms

    def test_algorithm_selection(self, qtbot: QtBot) -> None:
        """Test algorithm selection updates calculator."""
        dialog = ChecksumDialog()
        qtbot.addWidget(dialog)

        # Select SHA256
        dialog.m_rbSHA256.setChecked(True)
        dialog._on_algorithm_changed()

        assert dialog.calculator.current_algorithm == HashAlgorithm.SHA256

    def test_string_checksum_generation(self, qtbot: QtBot) -> None:
        """Test string checksum generation through GUI."""
        dialog = ChecksumDialog()
        qtbot.addWidget(dialog)

        # Set input string
        test_string = "hello world"
        dialog.m_leString.setText(test_string)

        # Generate checksum
        dialog._generate_string_checksum()

        # Verify result
        result_text = dialog.m_teChecksum.toPlainText()
        expected_hash = "5eb63bbbe01eeed093cb22bb8f5acdc3"  # MD5 of "hello world"
        assert expected_hash in result_text

    def test_file_dialog_opens(self, qtbot: QtBot) -> None:
        """Test file dialog method exists and is callable without opening dialog."""
        dialog = ChecksumDialog()
        qtbot.addWidget(dialog)

        # Test that the method exists and is callable
        assert hasattr(dialog, "_open_file_dialog")
        assert callable(dialog._open_file_dialog)

        # Test that calling the method doesn't raise an exception
        # We don't actually call it to avoid opening the dialog
        # The method signature and existence are what we're testing

    def test_file_selection_updates_ui(self, qtbot: QtBot) -> None:
        """Test that file selection updates the UI elements correctly."""
        dialog = ChecksumDialog()
        qtbot.addWidget(dialog)

        # Create a temporary test file
        with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".txt") as f:
            f.write("test content")
            test_file_path = Path(f.name)

        try:
            # Simulate file selection by directly setting the path
            # (In real usage, this would come from QFileDialog)
            dialog.current_file_path = str(test_file_path)
            dialog.m_leFile.setText(str(test_file_path))

            # Verify UI elements are updated correctly
            assert dialog.current_file_path == str(test_file_path)
            assert dialog.m_leFile.text() == str(test_file_path)

            # Test file checksum generation
            dialog._generate_file_checksum()
            result_text = dialog.m_teChecksum.toPlainText()

            # Verify checksum is calculated (MD5 of "test content")
            expected_hash = "9473fdd0d880a43c21b7778d34872157"
            assert expected_hash in result_text

        finally:
            # Clean up temporary file
            test_file_path.unlink()

    def test_file_selection_with_nonexistent_file(self, qtbot: QtBot) -> None:
        """Test file selection behavior with nonexistent file."""
        dialog = ChecksumDialog()
        qtbot.addWidget(dialog)

        # Set a nonexistent file path
        nonexistent_path = "/nonexistent/test_file.txt"
        dialog.current_file_path = nonexistent_path
        dialog.m_leFile.setText(nonexistent_path)

        # Generate checksum - should show error message
        dialog._generate_file_checksum()
        result_text = dialog.m_teChecksum.toPlainText()

        # Verify error message is displayed
        assert "Please select a valid file" in result_text

    def test_comparison_toggle(self, qtbot: QtBot) -> None:
        """Test comparison feature toggle."""
        dialog = ChecksumDialog()
        qtbot.addWidget(dialog)

        # Initially disabled
        assert dialog.enable_comparison is False

        # Toggle on
        dialog.m_cbEnableCompare.setChecked(True)
        dialog._toggle_comparison()
        assert dialog.enable_comparison is True

        # Toggle off
        dialog.m_cbEnableCompare.setChecked(False)
        dialog._toggle_comparison()
        assert dialog.enable_comparison is False


class TestCommandLineInterface:
    """Tests for command line interface functionality."""

    def test_import_main_function(self) -> None:
        """Test that main function can be imported."""
        assert callable(main)

    def test_import_parse_cli_arguments(self) -> None:
        """Test that argument parser can be imported."""
        assert callable(parse_cli_arguments)

    def test_import_run_functions(self) -> None:
        """Test that CLI run functions can be imported."""
        assert callable(run_cli_mode)
        assert callable(gui_main)
