"""Tests for certisigma_census.tagging — structured tags."""

from __future__ import annotations

import pytest
from unittest.mock import MagicMock, patch

from certisigma_census.tagging import (
    parse_tag_pair,
    set_tags,
    get_tags,
    delete_tag,
    query_tags,
    TagEntry,
    TagSetResult,
    TagQueryResult,
)


class TestParseTagPair:
    def test_simple(self):
        assert parse_tag_pair("dept=legal") == ("dept", "legal")

    def test_value_with_equals(self):
        assert parse_tag_pair("note=a=b=c") == ("note", "a=b=c")

    def test_empty_value(self):
        assert parse_tag_pair("key=") == ("key", "")

    def test_whitespace_stripped(self):
        assert parse_tag_pair(" dept = legal ") == ("dept", "legal")

    def test_missing_equals(self):
        with pytest.raises(ValueError, match="key=value"):
            parse_tag_pair("no_equals_here")

    def test_empty_key(self):
        with pytest.raises(ValueError, match="empty"):
            parse_tag_pair("=value")


class TestSetTags:
    @patch("certisigma.CertiSigmaClient")
    def test_success(self, MockClient):
        mock_result = MagicMock()
        mock_result.tags_upserted = 2
        MockClient.return_value.put_tags.return_value = mock_result

        result = set_tags("att_1", [("dept", "legal"), ("case", "001")], api_key="cs_test")
        assert result.success is True
        assert result.tags_upserted == 2
        call_args = MockClient.return_value.put_tags.call_args
        sent_tags = call_args[1]["tags"] if "tags" in call_args[1] else call_args[0][1]
        assert isinstance(sent_tags, list)
        assert sent_tags[0] == {"key": "dept", "value": "legal"}
        assert sent_tags[1] == {"key": "case", "value": "001"}

    def test_empty_tags(self):
        result = set_tags("att_1", [])
        assert result.success is False
        assert "No tags" in result.error

    @patch("certisigma.CertiSigmaClient")
    def test_api_error(self, MockClient):
        from certisigma import CertiSigmaError
        MockClient.return_value.put_tags.side_effect = CertiSigmaError("fail")
        result = set_tags("att_1", [("k", "v")], api_key="cs_test")
        assert result.success is False

    @patch("certisigma.CertiSigmaClient")
    @patch("certisigma.crypto.encrypt_metadata")
    @patch("certisigma_census.tagging._resolve_encryption_key", return_value="ab" * 32)
    def test_encrypted_tags(self, _mock_key, mock_encrypt, MockClient):
        mock_encrypt.return_value = {"ciphertext": "ct", "nonce": "n", "v": 1}
        mock_result = MagicMock()
        mock_result.tags_upserted = 1
        MockClient.return_value.put_tags.return_value = mock_result

        result = set_tags("att_1", [("secret", "data")], api_key="cs_test", encrypt=True)
        assert result.success is True
        mock_encrypt.assert_called_once()
        call_args = MockClient.return_value.put_tags.call_args
        sent_tags = call_args[1]["tags"] if "tags" in call_args[1] else call_args[0][1]
        assert isinstance(sent_tags, list)
        assert sent_tags[0]["key"] == "secret"
        assert sent_tags[0]["value_enc"] == "ct"
        assert sent_tags[0]["value_nonce"] == "n"
        assert sent_tags[0]["client_encrypted"] is True
        assert "value" not in sent_tags[0]

    @patch("certisigma_census.tagging._resolve_encryption_key", side_effect=ValueError("No encryption key"))
    def test_encrypted_missing_key(self, _mock_key):
        result = set_tags("att_1", [("secret", "data")], encrypt=True)
        assert result.success is False
        assert "Encryption key error" in result.error


class TestGetTags:
    @patch("certisigma.CertiSigmaClient")
    def test_returns_entries(self, MockClient):
        t = MagicMock()
        t.key = "dept"
        t.value = "legal"
        t.client_encrypted = False
        t.value_enc = None
        t.value_nonce = None
        t.created_at = "2026-03-01"
        t.updated_at = "2026-03-01"
        MockClient.return_value.get_tags.return_value = [t]

        entries = get_tags("att_1", api_key="cs_test")
        assert len(entries) == 1
        assert entries[0].key == "dept"
        assert entries[0].value == "legal"

    @patch("certisigma.CertiSigmaClient")
    def test_empty_tags(self, MockClient):
        MockClient.return_value.get_tags.return_value = []
        entries = get_tags("att_1", api_key="cs_test")
        assert entries == []

    @patch("certisigma.CertiSigmaClient")
    def test_api_error_returns_empty(self, MockClient):
        from certisigma import CertiSigmaError
        MockClient.return_value.get_tags.side_effect = CertiSigmaError("fail")
        entries = get_tags("att_1", api_key="cs_test")
        assert entries == []


class TestDeleteTag:
    @patch("certisigma.CertiSigmaClient")
    def test_success(self, MockClient):
        MockClient.return_value.delete_tag.return_value = None
        result = delete_tag("att_1", "dept", api_key="cs_test")
        assert result.success is True

    @patch("certisigma.CertiSigmaClient")
    def test_error(self, MockClient):
        from certisigma import CertiSigmaError
        MockClient.return_value.delete_tag.side_effect = CertiSigmaError("not found")
        result = delete_tag("att_1", "bad", api_key="cs_test")
        assert result.success is False


class TestQueryTags:
    @patch("certisigma.CertiSigmaClient")
    def test_returns_results(self, MockClient):
        mock_result = MagicMock()
        mock_result.attestation_ids = ["att_1", "att_2"]
        mock_result.next_cursor = "cur_abc"
        mock_result.count = 2
        MockClient.return_value.query_tags.return_value = mock_result

        result = query_tags([("dept", "legal")], api_key="cs_test", limit=50)
        assert result.count == 2
        assert result.next_cursor == "cur_abc"
        assert len(result.attestation_ids) == 2

    @patch("certisigma.CertiSigmaClient")
    def test_error_returns_error_field(self, MockClient):
        from certisigma import CertiSigmaError
        MockClient.return_value.query_tags.side_effect = CertiSigmaError("fail")
        result = query_tags([("k", "v")], api_key="cs_test")
        assert result.count == 0
        assert result.attestation_ids == []
        assert result.error is not None
        assert "fail" in result.error
