"""Tests for crypto error paths and asset reader behavior."""

from __future__ import annotations

import struct
from pathlib import Path

import pytest

from noteflow.infrastructure.security.crypto import (
    FILE_MAGIC,
    FILE_VERSION,
    MIN_CHUNK_LENGTH,
    AesGcmCryptoBox,
    ChunkedAssetReader,
    ChunkedAssetWriter,
)

# crypto fixture is provided by tests/conftest.py


class TestAesGcmCryptoBox:
    """Tests for AesGcmCryptoBox edge cases."""

    def test_unwrap_dek_too_short_raises(self, crypto: AesGcmCryptoBox) -> None:
        """unwrap_dek rejects payloads shorter than nonce+ciphertext+tag."""
        with pytest.raises(ValueError, match="Invalid wrapped DEK"):
            crypto.unwrap_dek(b"short")


class TestChunkedAssetReader:
    """Tests for ChunkedAssetReader validation."""

    def test_open_invalid_magic_raises(self, crypto: AesGcmCryptoBox, tmp_path: Path) -> None:
        """Reader rejects files with invalid magic."""
        path = tmp_path / "bad_magic.enc"
        path.write_bytes(b"BAD!" + bytes([FILE_VERSION]))

        reader = ChunkedAssetReader(crypto)
        with pytest.raises(ValueError, match="Invalid file format"):
            reader.open(path, crypto.generate_dek())

    def test_open_invalid_version_raises(self, crypto: AesGcmCryptoBox, tmp_path: Path) -> None:
        """Reader rejects unsupported file versions."""
        path = tmp_path / "bad_version.enc"
        path.write_bytes(FILE_MAGIC + bytes([FILE_VERSION + 1]))

        reader = ChunkedAssetReader(crypto)
        with pytest.raises(ValueError, match="Unsupported file version"):
            reader.open(path, crypto.generate_dek())

    def test_read_truncated_chunk_raises(self, crypto: AesGcmCryptoBox, tmp_path: Path) -> None:
        """Reader errors on truncated chunk data."""
        path = tmp_path / "truncated.enc"
        valid_chunk_length = MIN_CHUNK_LENGTH + 10  # Valid length (38 bytes)
        truncated_data = b"x" * (valid_chunk_length - 5)  # Only 33 bytes provided

        with path.open("wb") as handle:
            handle.write(FILE_MAGIC)
            handle.write(struct.pack("B", FILE_VERSION))
            handle.write(struct.pack(">I", valid_chunk_length))
            handle.write(truncated_data)

        reader = ChunkedAssetReader(crypto)
        reader.open(path, crypto.generate_dek())
        with pytest.raises(ValueError, match="Truncated chunk"):
            list(reader.read_chunks())

        reader.close()

    def test_read_invalid_chunk_length_raises(
        self, crypto: AesGcmCryptoBox, tmp_path: Path
    ) -> None:
        """Reader rejects chunk length smaller than minimum (nonce + tag)."""
        path = tmp_path / "invalid_chunk_length.enc"
        invalid_length = MIN_CHUNK_LENGTH - 1  # One byte too small

        with path.open("wb") as handle:
            handle.write(FILE_MAGIC)
            handle.write(struct.pack("B", FILE_VERSION))
            # Write a chunk length that is too small to be valid
            handle.write(struct.pack(">I", invalid_length))
            # Provide the claimed bytes (doesn't matter, length check comes first)
            handle.write(b"x" * invalid_length)

        reader = ChunkedAssetReader(crypto)
        reader.open(path, crypto.generate_dek())

        with pytest.raises(ValueError, match="Invalid chunk length"):
            list(reader.read_chunks())

        reader.close()

    def test_read_with_wrong_dek_raises(self, crypto: AesGcmCryptoBox, tmp_path: Path) -> None:
        """Decrypting with the wrong key fails."""
        path = tmp_path / "wrong_key.enc"
        dek = crypto.generate_dek()
        other_dek = crypto.generate_dek()

        writer = ChunkedAssetWriter(crypto)
        writer.open(path, dek)
        writer.write_chunk(b"hello")
        writer.close()

        reader = ChunkedAssetReader(crypto)
        reader.open(path, other_dek)
        with pytest.raises(ValueError, match="Chunk decryption failed"):
            list(reader.read_chunks())
        reader.close()
