"""Tests for forensic annotation (metadata update/delete/encrypt)."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Optional
from unittest.mock import MagicMock, patch

import pytest

from certisigma_census.annotate import (
    AnnotationResult,
    _build_extra_data,
    annotate,
    decrypt_annotation,
    delete_annotation,
)


@dataclass
class _MockMetadataResult:
    success: bool = True
    claim_id: str = "claim_1"
    attestation_id: str = "att_123"
    source: Optional[str] = None
    extra_data: Optional[dict] = None
    client_encrypted: bool = False
    deleted: bool = False
    audit_entries: int = 1


class TestBuildExtraData:
    def test_empty(self):
        assert _build_extra_data() == {}

    def test_note_only(self):
        assert _build_extra_data(note="evidence collected") == {"note": "evidence collected"}

    def test_all_fields(self):
        d = _build_extra_data(note="n", tag="t", case_id="c", custom={"k": "v"})
        assert d == {"note": "n", "tag": "t", "case_id": "c", "k": "v"}

    def test_custom_overwrites(self):
        d = _build_extra_data(note="n", custom={"note": "override"})
        assert d["note"] == "override"


class TestAnnotate:
    @patch("certisigma.CertiSigmaClient")
    def test_basic_annotation(self, mock_cls):
        client = MagicMock()
        mock_cls.return_value = client
        client.update_metadata.return_value = _MockMetadataResult(
            source="forensics",
            extra_data={"note": "test"},
        )

        result = annotate("att_123", api_key="cs_test", note="test", source="forensics")
        assert result.success
        assert result.attestation_id == "att_123"
        client.update_metadata.assert_called_once()

    @patch("certisigma.CertiSigmaClient")
    def test_error_handling(self, mock_cls):
        from certisigma import CertiSigmaError

        client = MagicMock()
        mock_cls.return_value = client
        client.update_metadata.side_effect = CertiSigmaError("forbidden")

        result = annotate("att_123", api_key="cs_test", note="test")
        assert not result.success
        assert "forbidden" in result.error

    @patch("certisigma.CertiSigmaClient")
    @patch("certisigma.crypto.encrypt_metadata")
    def test_encrypted_annotation(self, mock_encrypt, mock_cls):
        client = MagicMock()
        mock_cls.return_value = client
        mock_encrypt.return_value = {"ciphertext": "abc", "nonce": "def", "v": 1}
        client.update_metadata.return_value = _MockMetadataResult(
            client_encrypted=True,
            extra_data={"ciphertext": "abc", "nonce": "def", "v": 1},
        )

        result = annotate(
            "att_123", api_key="cs_test", note="secret",
            encrypt=True, encryption_key="a" * 64,
        )
        assert result.success
        assert result.client_encrypted
        mock_encrypt.assert_called_once()
        call_kwargs = client.update_metadata.call_args
        assert call_kwargs[1].get("client_encrypted") is True


class TestDeleteAnnotation:
    @patch("certisigma.CertiSigmaClient")
    def test_soft_delete(self, mock_cls):
        client = MagicMock()
        mock_cls.return_value = client
        client.delete_metadata.return_value = _MockMetadataResult(deleted=True)

        result = delete_annotation("att_123", api_key="cs_test")
        assert result.success
        assert result.deleted

    @patch("certisigma.CertiSigmaClient")
    def test_delete_error(self, mock_cls):
        from certisigma import CertiSigmaError

        client = MagicMock()
        mock_cls.return_value = client
        client.delete_metadata.side_effect = CertiSigmaError("not found")

        result = delete_annotation("att_123", api_key="cs_test")
        assert not result.success
        assert "not found" in result.error


class TestDecryptAnnotation:
    def test_passthrough_unencrypted(self):
        data = {"note": "plain text"}
        assert decrypt_annotation(data, encryption_key="a" * 64) == data

    @patch("certisigma.crypto.is_encrypted", return_value=True)
    @patch("certisigma.crypto.decrypt_metadata")
    def test_decrypts_encrypted_envelope(self, mock_decrypt, mock_is_enc):
        envelope = {"ciphertext": "abc", "nonce": "def", "v": 1}
        mock_decrypt.return_value = {"note": "decrypted"}

        result = decrypt_annotation(envelope, encryption_key="a" * 64)
        assert result == {"note": "decrypted"}
        mock_decrypt.assert_called_once_with(envelope, "a" * 64)


class TestResolveEncryptionKey:
    def test_missing_key_returns_error(self):
        result = annotate("att_123", api_key="cs_test", note="x", encrypt=True)
        assert not result.success
        assert "Encryption failed" in result.error

    @patch("certisigma_census.config.load_config", return_value={"encryption_key": "b" * 64})
    @patch("certisigma.CertiSigmaClient")
    @patch("certisigma.crypto.encrypt_metadata", return_value={"ciphertext": "x", "nonce": "y", "v": 1})
    def test_key_from_config(self, mock_encrypt, mock_cls, mock_cfg):
        client = MagicMock()
        mock_cls.return_value = client
        client.update_metadata.return_value = _MockMetadataResult(client_encrypted=True)

        result = annotate("att_123", api_key="cs_test", note="x", encrypt=True)
        assert result.success
        mock_encrypt.assert_called_once()
        used_key = mock_encrypt.call_args[0][1]
        assert used_key == "b" * 64
