"""
:mod:`tests.unit.file.test_u_file_zip` module.

Unit tests for :mod:`etlplus.file.zip`.
"""

from __future__ import annotations

import zipfile
from pathlib import Path

import pytest

from etlplus.file import core
from etlplus.file import zip as mod
from etlplus.file.enums import FileFormat

# SECTION: HELPERS ========================================================== #


class _StubFile:
    """Minimal stand-in for :class:`etlplus.file.core.File`."""

    # pylint: disable=unused-argument

    def __init__(
        self,
        path: Path,
        fmt: FileFormat,
    ) -> None:
        self.path = Path(path)
        self.fmt = fmt

    def read(self) -> dict[str, str]:
        """Return deterministic payload for smoke reads."""
        return {'fmt': self.fmt.value, 'name': self.path.name}

    def write(
        self,
        data: object,
    ) -> int:  # noqa: ARG002
        """Persist dummy bytes so ZIP write can read them."""
        self.path.write_text('payload', encoding='utf-8')
        return 1


def _write_zip(
    path: Path,
    entries: dict[str, bytes],
) -> None:
    """Helper to write a ZIP archive with specified entries."""
    with zipfile.ZipFile(
        path,
        'w',
        compression=zipfile.ZIP_DEFLATED,
    ) as archive:
        for name, payload in entries.items():
            archive.writestr(name, payload)


# SECTION: TESTS ============================================================ #


class TestZipRead:
    """Unit tests for :func:`etlplus.file.zip.read`."""

    def test_read_raises_on_empty_archive(
        self,
        tmp_path: Path,
    ) -> None:
        """Test that reading an empty ZIP archive raises an error."""
        path = tmp_path / 'empty.zip'
        _write_zip(path, {})

        with pytest.raises(ValueError, match='ZIP archive is empty'):
            mod.read(path)

    def test_read_raises_on_unexpected_compression(
        self,
        tmp_path: Path,
    ) -> None:
        """
        Test that reading a ZIP archive with unexpected compression raises an
        error.
        """
        path = tmp_path / 'nested.zip'
        _write_zip(path, {'data.csv.gz': b'ignored'})

        with pytest.raises(ValueError, match='Unexpected compression'):
            mod.read(path)

    def test_read_raises_on_unknown_inner_format(
        self,
        tmp_path: Path,
    ) -> None:
        """
        Test that reading a ZIP archive with unknown inner format raises an
        error.
        """
        path = tmp_path / 'payload.zip'
        _write_zip(path, {'payload.unknown': b'ignored'})

        with pytest.raises(ValueError, match='Cannot infer file format'):
            mod.read(path)

    def test_read_single_entry_uses_file_helper(
        self,
        tmp_path: Path,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """
        Test that reading a ZIP archive with a single entry uses the file
        helper.
        """
        monkeypatch.setattr(core, 'File', _StubFile)
        path = tmp_path / 'payload.zip'
        _write_zip(path, {'payload.json': b'{}'})

        result = mod.read(path)

        assert result == {'fmt': 'json', 'name': 'payload.json'}

    def test_read_multiple_entries_returns_mapping(
        self,
        tmp_path: Path,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """
        Test that reading a ZIP archive with multiple entries returns a mapping
        of file names to their contents.
        """
        monkeypatch.setattr(core, 'File', _StubFile)
        path = tmp_path / 'payloads.zip'
        _write_zip(path, {'a.json': b'{}', 'b.json': b'{}'})

        result = mod.read(path)

        assert result == {
            'a.json': {'fmt': 'json', 'name': 'a.json'},
            'b.json': {'fmt': 'json', 'name': 'b.json'},
        }


class TestZipWrite:
    """Unit tests for :func:`etlplus.file.zip.write`."""

    def test_write_requires_inner_format(
        self,
        tmp_path: Path,
    ) -> None:
        """Test that writing requires an inner file format."""
        path = tmp_path / 'payload.zip'

        with pytest.raises(ValueError, match='Cannot infer file format'):
            mod.write(path, [{'id': 1}])

    def test_write_creates_zip_with_inner_payload(
        self,
        tmp_path: Path,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """Test that writing creates a ZIP archive with the inner payload."""
        monkeypatch.setattr(core, 'File', _StubFile)
        path = tmp_path / 'payload.json.zip'

        written = mod.write(path, [{'id': 1}])

        assert written == 1
        with zipfile.ZipFile(path, 'r') as archive:
            assert archive.namelist() == ['payload.json']
            assert archive.read('payload.json') == b'payload'
