"""Unit tests for pdfcrypt module."""

from __future__ import annotations

import atexit
from pathlib import Path
from unittest.mock import Mock, patch

import pytest

from pytola.office.pdfcrypt.pdfcrypt import (
    PDFCryptConfig,
    _cleanup_resources,
    _handle_output_file_conflict,
    _setup_pdf_writer,
    _validate_password_strength,
    decrypt_pdf,
    encrypt_pdf,
    is_encrypted,
    list_pdf,
)


@pytest.fixture(autouse=True)
def setup_test_logging():
    """Set up clean logging environment for each test."""
    # Remove the atexit handler that saves config
    import pytola.office.pdfcrypt.pdfcrypt as pdfcrypt_module

    atexit.unregister(pdfcrypt_module.conf.save)

    # Reset logger configuration
    pdfcrypt_module.logger.handlers.clear()
    pdfcrypt_module.logger.propagate = True

    yield

    # Re-register the handler after test
    atexit.register(pdfcrypt_module.conf.save)


@pytest.fixture
def sample_pdf(tmp_path):
    """Create a sample PDF file for testing."""
    from pypdf import PdfWriter

    pdf_path = tmp_path / "sample.pdf"
    writer = PdfWriter()
    writer.add_blank_page(width=200, height=200)
    with open(pdf_path, "wb") as f:
        writer.write(f)
    return pdf_path


class TestPDFCryptConfig:
    """Tests for PDFCryptConfig class."""

    def test_config_initialization_defaults(self):
        """Test configuration initializes with default values."""
        config = PDFCryptConfig()
        assert config.max_workers == 4
        assert config.default_password == ""
        assert config.output_suffix_encrypted == ".enc.pdf"
        assert config.output_suffix_decrypted == ".dec.pdf"

    def test_config_custom_values(self):
        """Test configuration accepts custom values."""
        # Test with fresh instance that won't load existing config
        config = PDFCryptConfig.__new__(PDFCryptConfig)
        config.max_workers = 8
        config.default_password = "test123"
        config.output_suffix_encrypted = ".encrypted.pdf"
        config.output_suffix_decrypted = ".decrypted.pdf"
        config._config_loaded = False

        assert config.max_workers == 8
        assert config.default_password == "test123"
        assert config.output_suffix_encrypted == ".encrypted.pdf"
        assert config.output_suffix_decrypted == ".decrypted.pdf"

    def test_is_configured_property(self):
        """Test is_configured property works correctly."""
        config = PDFCryptConfig()
        config._config_loaded = False  # Reset for testing
        # Should be False when config is not loaded
        assert config.is_configured is False

    def test_config_summary_property(self):
        """Test config_summary property provides correct information."""
        config = PDFCryptConfig()
        config._config_loaded = False  # Reset for testing
        summary = config.config_summary
        assert isinstance(summary, dict)
        assert "max_workers" in summary
        assert "output_suffix_encrypted" in summary
        assert "output_suffix_decrypted" in summary
        assert "source" in summary
        assert summary["source"] == "default"


class TestHelperFunctions:
    """Tests for helper functions."""

    @patch("pytola.office.pdfcrypt.pdfcrypt.logger")
    def test_validate_password_strength_weak_password(self, mock_logger):
        """Test password strength validation with weak password."""
        _validate_password_strength("123")
        # Check that warning was logged
        mock_logger.warning.assert_called_once()
        call_args = mock_logger.warning.call_args[0][0]
        assert "Password length less than" in call_args

    @patch("pytola.office.pdfcrypt.pdfcrypt.logger")
    def test_validate_password_strength_strong_password(self, mock_logger):
        """Test password strength validation with strong password."""
        _validate_password_strength("strongpassword123")
        # Should not log any warnings
        mock_logger.warning.assert_not_called()

    @patch("pytola.office.pdfcrypt.pdfcrypt.pypdf.PdfReader")
    @patch("pytola.office.pdfcrypt.pdfcrypt.pypdf.PdfWriter")
    def test_setup_pdf_writer(self, mock_writer_class, mock_reader):
        """Test PDF writer setup function."""
        mock_reader.pages = [Mock(), Mock()]  # Two mock pages
        mock_writer = Mock()
        mock_writer_class.return_value = mock_writer

        writer = _setup_pdf_writer(mock_reader)
        assert writer == mock_writer
        assert mock_writer.add_page.call_count == 2

    @patch("pytola.office.pdfcrypt.pdfcrypt.logger")
    def test_handle_output_file_conflict_exists(self, mock_logger, tmp_path):
        """Test handling of existing output file."""
        test_file = tmp_path / "test.txt"
        test_file.touch()  # Create the file
        _handle_output_file_conflict(test_file)
        # Check that warning was logged
        mock_logger.warning.assert_called_once()
        call_args = mock_logger.warning.call_args[0][0]
        assert "Target file already exists" in call_args

    @patch("pytola.office.pdfcrypt.pdfcrypt.logger")
    def test_handle_output_file_conflict_not_exists(self, mock_logger, tmp_path):
        """Test handling of non-existing output file."""
        test_file = tmp_path / "nonexistent.txt"
        _handle_output_file_conflict(test_file)
        # Should not log any warnings
        mock_logger.warning.assert_not_called()

    @patch("pytola.office.pdfcrypt.pdfcrypt.contextlib.suppress")
    def test_cleanup_resources_with_reader(self, mock_suppress):
        """Test resource cleanup with reader."""
        mock_reader = Mock()
        mock_reader.stream.close = Mock()
        _cleanup_resources(reader=mock_reader)
        mock_reader.stream.close.assert_called_once()

    @patch("pytola.office.pdfcrypt.pdfcrypt.contextlib.suppress")
    def test_cleanup_resources_with_writer(self, mock_suppress):
        """Test resource cleanup with writer."""
        mock_writer = Mock()
        mock_writer.close = Mock()
        _cleanup_resources(writer=mock_writer)
        mock_writer.close.assert_called_once()


class TestEncryptionDecryption:
    """Tests for encryption and decryption functionality."""

    def test_is_encrypted_with_unencrypted_file(self, sample_pdf):
        """Test is_encrypted function with unencrypted file."""
        result = is_encrypted(sample_pdf)
        assert result is False

    def test_encrypt_pdf_success(self, sample_pdf, tmp_path):
        """Test successful PDF encryption."""
        password = "testpassword123"
        # Use a temporary file name to avoid conflicts
        test_pdf = tmp_path / "test_sample.pdf"
        test_pdf.write_bytes(sample_pdf.read_bytes())

        original_path, encrypted_path = encrypt_pdf(test_pdf, password)

        assert original_path == test_pdf
        assert encrypted_path is not None
        assert encrypted_path.suffixes == [".enc", ".pdf"] or ".enc.pdf" in encrypted_path.name
        assert encrypted_path.exists()

    @patch("pytola.office.pdfcrypt.pdfcrypt.logger")
    def test_encrypt_pdf_weak_password(self, mock_logger, sample_pdf):
        """Test PDF encryption with weak password logs warning."""
        password = "weak"
        encrypt_pdf(sample_pdf, password)
        # Check that warning was logged
        # Find the warning call with password message
        warning_calls = [
            call for call in mock_logger.warning.call_args_list if "Password length less than" in call[0][0]
        ]
        assert len(warning_calls) == 1

    def test_decrypt_pdf_wrong_password(self, sample_pdf):
        """Test PDF decryption with wrong password."""
        # First encrypt a file
        password = "correctpassword"
        _, encrypted_path = encrypt_pdf(sample_pdf, password)

        # Try to decrypt with wrong password
        original_path, decrypted_path = decrypt_pdf(encrypted_path, "wrongpassword")
        assert original_path == encrypted_path
        assert decrypted_path is None

    def test_list_pdf_empty_directory(self, tmp_path):
        """Test listing PDF files in empty directory."""
        with patch("pytola.office.pdfcrypt.pdfcrypt.CWD", tmp_path):
            unencrypted, encrypted = list_pdf()
            assert unencrypted == []
            assert encrypted == []


class TestIntegration:
    """Integration tests for complete workflow."""

    @pytest.fixture
    def test_directory(self, tmp_path):
        """Create test directory with sample files."""
        # Change working directory for the test
        original_cwd = Path.cwd()
        import os

        os.chdir(tmp_path)
        yield tmp_path
        os.chdir(original_cwd)

    def test_complete_encrypt_decrypt_cycle(self, test_directory, sample_pdf):
        """Test complete encrypt -> decrypt cycle."""
        password = "integrationtest123"

        # Copy sample PDF to test directory
        test_pdf = test_directory / "test_document.pdf"
        test_pdf.write_bytes(sample_pdf.read_bytes())

        # Encrypt the file
        _original_path, encrypted_path = encrypt_pdf(test_pdf, password)
        assert encrypted_path is not None
        assert encrypted_path.exists()

        # Decrypt the file
        _decrypted_original, decrypted_path = decrypt_pdf(encrypted_path, password)
        assert decrypted_path is not None
        assert decrypted_path.exists()

        # Verify the decrypted file exists and has content
        assert decrypted_path.stat().st_size > 0


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
