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

from pathlib import Path
from typing import Generator
from unittest.mock import Mock, patch

import pytest

from pytola_office.pdfcrypt.cli import (
    _cleanup_resources,
    _handle_output_file_conflict,
    _setup_pdf_writer,
    _validate_password_strength,
    decrypt_pdf,
    encrypt_pdf,
    is_encrypted,
    list_pdf,
)


@pytest.fixture
def sample_pdf(tmp_path) -> 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 Path(pdf_path).open("wb") as f:
        writer.write(f)
    return pdf_path


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

    @patch("pytola_office.pdfcrypt.cli.logger")
    def test_validate_password_strength_weak_password(self, mock_logger: pytest.FixtureRequest) -> None:
        """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.cli.logger")
    def test_validate_password_strength_strong_password(self, mock_logger: pytest.FixtureRequest) -> None:
        """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.cli.pypdf.PdfReader")
    @patch("pytola_office.pdfcrypt.cli.pypdf.PdfWriter")
    def test_setup_pdf_writer(self, mock_writer_class, mock_reader) -> None:
        """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.cli.logger")
    def test_handle_output_file_conflict_exists(self, mock_logger, tmp_path) -> None:
        """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.cli.logger")
    def test_handle_output_file_conflict_not_exists(self, mock_logger, tmp_path) -> None:
        """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.cli.contextlib.suppress")
    def test_cleanup_resources_with_reader(self, mock_suppress: pytest.FixtureRequest) -> None:
        """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.cli.contextlib.suppress")
    def test_cleanup_resources_with_writer(self, mock_suppress: pytest.FixtureRequest) -> None:
        """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: Path) -> None:
        """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) -> None:
        """Test successful PDF encryption."""
        password = "testpassword123"  # noqa: S105
        # 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.cli.logger")
    def test_encrypt_pdf_weak_password(self, mock_logger, sample_pdf) -> None:
        """Test PDF encryption with weak password logs warning."""
        password = "weak"  # noqa: S105
        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: Path) -> None:
        """Test PDF decryption with wrong password."""
        # First encrypt a file
        password = "correctpassword"  # noqa: S105
        _, 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: Path) -> None:
        """Test listing PDF files in empty directory."""
        with patch("pytola_office.pdfcrypt.cli.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) -> Generator[Path, None, None]:
        """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) -> None:
        """Test complete encrypt -> decrypt cycle."""
        password = "integrationtest123_example%@@"  # noqa: S105

        # 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"])
