# SPDX-FileCopyrightText: © 2026 Joe T. Sylve, Ph.D. <joe.sylve@gmail.com>
#
# SPDX-License-Identifier: AGPL-3.0-or-later

"""Integration tests for PSpice decryption.

Test data was generated by Cadence PSpiceEnc (PSpice TI, version string "3")
from a minimal .subckt model file.  Each mode*.lib file was encrypted with
PSpiceEnc -m <N> and the mode4_userkey.lib was encrypted with -m 4 and
CDN_PSPICE_ENCKEYS pointing to a CSV with user key
``0123456789abcdefghijklmnopqrstu``.
"""

from __future__ import annotations

from pathlib import Path

import pytest

from spice_crypt import decrypt_stream
from spice_crypt.pspice.decrypt import PSpiceFileParser
from tests.conftest import PLAINTEXT_BODY, extract_body

DATA_DIR = Path(__file__).parent / "data" / "pspice"

USER_KEY = b"0123456789abcdefghijklmnopqrstu"


def _decrypt_file(path: Path, *, user_key: bytes | None = None) -> str:
    """Decrypt a PSpice file and return the full decrypted text."""
    content, _ = decrypt_stream(str(path), user_key=user_key)
    return content


# ---------------------------------------------------------------------------
# Decryption tests for each PSpice encryption mode
# ---------------------------------------------------------------------------


class TestPSpiceDecryption:
    """Verify decryption of PSpiceEnc-generated files across all modes."""

    def test_mode0_des_legacy(self):
        result = _decrypt_file(DATA_DIR / "mode0.lib")
        assert extract_body(result) == PLAINTEXT_BODY

    def test_mode2_des_adv(self):
        result = _decrypt_file(DATA_DIR / "mode2.lib")
        assert extract_body(result) == PLAINTEXT_BODY

    def test_mode3_aes_default(self):
        result = _decrypt_file(DATA_DIR / "mode3.lib")
        assert extract_body(result) == PLAINTEXT_BODY

    def test_mode4_aes_no_user_key(self):
        result = _decrypt_file(DATA_DIR / "mode4.lib")
        assert extract_body(result) == PLAINTEXT_BODY

    def test_mode4_aes_user_key(self):
        result = _decrypt_file(DATA_DIR / "mode4_userkey.lib", user_key=USER_KEY)
        assert extract_body(result) == PLAINTEXT_BODY

    def test_mode4_user_key_wrong_key_fails(self):
        wrong_key = b"wrong_key_padded_to_31_bytes!!!!"
        result = _decrypt_file(DATA_DIR / "mode4_userkey.lib", user_key=wrong_key)
        try:
            body = extract_body(result)
        except StopIteration:
            return  # Garbage output with no .subckt/.ends markers — decryption failed as expected
        assert body != PLAINTEXT_BODY


# ---------------------------------------------------------------------------
# Format auto-detection
# ---------------------------------------------------------------------------


class TestAutoDetection:
    """Verify that decrypt_stream auto-detects PSpice format."""

    @pytest.mark.parametrize("filename", ["mode0.lib", "mode2.lib", "mode3.lib", "mode4.lib"])
    def test_auto_detect(self, filename):
        result = _decrypt_file(DATA_DIR / filename)
        assert "R1 1 2 1k" in result


# ---------------------------------------------------------------------------
# PSpiceFileParser direct usage
# ---------------------------------------------------------------------------


class TestPSpiceFileParser:
    """Test the PSpiceFileParser class directly."""

    def test_parser_stream(self):
        with open(DATA_DIR / "mode3.lib") as f:
            parser = PSpiceFileParser(f)
            chunks = list(parser.decrypt_stream())
        text = b"".join(chunks).decode("utf-8", "replace")
        assert "R1 1 2 1k" in text

    def test_parser_with_user_key(self):
        with open(DATA_DIR / "mode4_userkey.lib") as f:
            parser = PSpiceFileParser(f, user_key_bytes=USER_KEY)
            chunks = list(parser.decrypt_stream())
        text = b"".join(chunks).decode("utf-8", "replace")
        assert "R1 1 2 1k" in text


# ---------------------------------------------------------------------------
# Key recovery (Mode 4 brute-force)
# ---------------------------------------------------------------------------


class TestKeyRecovery:
    """Test Mode 4 AES brute-force key recovery."""

    def test_recover_user_key(self):
        from spice_crypt.pspice import recover_mode4_key

        result = recover_mode4_key(DATA_DIR / "mode4_userkey.lib")

        assert result is not None
        assert result.user_key_full == USER_KEY

    def test_recover_no_user_key_raises(self):
        from spice_crypt.pspice import recover_mode4_key

        # Mode 4 without user key uses default keys; recovery should
        # raise ValueError indicating no brute-force is needed.
        with pytest.raises(ValueError, match="default Mode 4 keys"):
            recover_mode4_key(DATA_DIR / "mode4.lib")
