"""Structured tagging — classify attestations with key-value tags.

Uses ``CertiSigmaClient.put_tags()``, ``get_tags()``,
``delete_tag()``, and ``query_tags()`` from SDK v1.6.0+.

Tags enable enterprise classification (department, sensitivity level,
case ID) and cross-attestation search.  Tags can be encrypted
client-side with AES-256-GCM for zero-knowledge classification.
"""

from __future__ import annotations

import logging
from dataclasses import dataclass, field
from typing import Any, Optional

logger = logging.getLogger(__name__)


@dataclass
class TagEntry:
    key: str
    value: str
    client_encrypted: bool = False
    created_at: Optional[str] = None
    updated_at: Optional[str] = None


@dataclass
class TagSetResult:
    attestation_id: str
    success: bool
    tags_upserted: int = 0
    error: Optional[str] = None


@dataclass
class TagQueryResult:
    attestation_ids: list[str] = field(default_factory=list)
    next_cursor: Optional[str] = None
    count: int = 0
    error: Optional[str] = None


MAX_TAG_KEY_LEN = 128
MAX_TAG_VALUE_LEN = 1024


def parse_tag_pair(raw: str) -> tuple[str, str]:
    """Parse ``key=value`` into a ``(key, value)`` tuple.

    Raises ``ValueError`` on missing ``=``, empty key, or excessive length.
    """
    if "=" not in raw:
        raise ValueError(f"Invalid tag format: {raw!r}. Use key=value.")
    key, _, value = raw.partition("=")
    key = key.strip()
    if not key:
        raise ValueError("Tag key cannot be empty.")
    if len(key) > MAX_TAG_KEY_LEN:
        raise ValueError(f"Tag key too long ({len(key)} chars, max {MAX_TAG_KEY_LEN}).")
    value = value.strip()
    if len(value) > MAX_TAG_VALUE_LEN:
        raise ValueError(f"Tag value too long ({len(value)} chars, max {MAX_TAG_VALUE_LEN}).")
    return key, value


def set_tags(
    attestation_id: str,
    tags: list[tuple[str, str]],
    *,
    api_key: str | None = None,
    base_url: str | None = None,
    encrypt: bool = False,
    encryption_key: str | None = None,
) -> TagSetResult:
    """Upsert tags on an attestation.

    When *encrypt* is True, tag values are encrypted client-side.
    """
    from certisigma import CertiSigmaClient, CertiSigmaError

    if not tags:
        return TagSetResult(
            attestation_id=attestation_id,
            success=False,
            error="No tags provided.",
        )

    tag_list: list[dict[str, Any]] = []
    if encrypt:
        try:
            resolved_key = _resolve_encryption_key(encryption_key)
        except (ValueError, TypeError) as exc:
            return TagSetResult(
                attestation_id=attestation_id,
                success=False,
                error=f"Encryption key error: {exc}",
            )
        from certisigma.crypto import encrypt_metadata
    for key, value in tags:
        if encrypt:
            try:
                envelope = encrypt_metadata({"v": value}, resolved_key)
                tag_list.append({
                    "key": key,
                    "value_enc": envelope["ciphertext"],
                    "value_nonce": envelope["nonce"],
                    "client_encrypted": True,
                })
            except (ValueError, TypeError, KeyError) as exc:
                return TagSetResult(
                    attestation_id=attestation_id,
                    success=False,
                    error=f"Encryption failed: {exc}",
                )
        else:
            tag_list.append({"key": key, "value": value})

    kwargs: dict[str, Any] = {}
    if api_key:
        kwargs["api_key"] = api_key
    if base_url:
        kwargs["base_url"] = base_url
    client = CertiSigmaClient(**kwargs)
    try:
        try:
            result = client.put_tags(attestation_id, tags=tag_list)
            return TagSetResult(
                attestation_id=attestation_id,
                success=True,
                tags_upserted=result.tags_upserted,
            )
        except CertiSigmaError as exc:
            return TagSetResult(
                attestation_id=attestation_id,
                success=False,
                error=str(exc),
            )
    finally:
        client.close()


def get_tags(
    attestation_id: str,
    *,
    api_key: str | None = None,
    base_url: str | None = None,
    decrypt: bool = False,
    encryption_key: str | None = None,
) -> list[TagEntry]:
    """List tags on an attestation."""
    from certisigma import CertiSigmaClient, CertiSigmaError

    kwargs: dict[str, Any] = {}
    if api_key:
        kwargs["api_key"] = api_key
    if base_url:
        kwargs["base_url"] = base_url
    client = CertiSigmaClient(**kwargs)
    try:
        try:
            raw_tags = client.get_tags(attestation_id)
        except CertiSigmaError as exc:
            logger.error("Failed to get tags for %s: %s", attestation_id, exc)
            return []
        entries = []
        resolved_key = None
        if decrypt:
            try:
                resolved_key = _resolve_encryption_key(encryption_key)
            except (ValueError, TypeError) as exc:
                logger.warning("Failed to resolve encryption key: %s", exc)
            from certisigma.crypto import decrypt_metadata
        for t in raw_tags:
            value = t.value or ""
            encrypted = t.client_encrypted
            if decrypt and encrypted and resolved_key is not None:
                try:
                    envelope = {
                        "ciphertext": t.value_enc,
                        "nonce": t.value_nonce,
                        "v": 1,
                    }
                    decrypted = decrypt_metadata(envelope, resolved_key)
                    value = decrypted.get("v", "")
                    encrypted = False
                except (ValueError, TypeError, KeyError, AttributeError) as exc:
                    logger.warning("Failed to decrypt tag %s: %s", t.key, exc)
                    value = f"[encrypted — decryption failed: {exc}]"
            entries.append(TagEntry(
                key=t.key,
                value=value,
                client_encrypted=encrypted,
                created_at=t.created_at,
                updated_at=t.updated_at,
            ))
        return entries
    finally:
        client.close()


def delete_tag(
    attestation_id: str,
    tag_key: str,
    *,
    api_key: str | None = None,
    base_url: str | None = None,
) -> TagSetResult:
    """Delete a specific tag from an attestation."""
    from certisigma import CertiSigmaClient, CertiSigmaError

    kwargs: dict[str, Any] = {}
    if api_key:
        kwargs["api_key"] = api_key
    if base_url:
        kwargs["base_url"] = base_url
    client = CertiSigmaClient(**kwargs)
    try:
        try:
            client.delete_tag(attestation_id, tag_key)
            return TagSetResult(
                attestation_id=attestation_id,
                success=True,
            )
        except CertiSigmaError as exc:
            return TagSetResult(
                attestation_id=attestation_id,
                success=False,
                error=str(exc),
            )
    finally:
        client.close()


def query_tags(
    filters: list[tuple[str, str]],
    *,
    api_key: str | None = None,
    base_url: str | None = None,
    limit: int = 100,
    cursor: str | None = None,
) -> TagQueryResult:
    """Query attestations by tag key-value combinations (AND logic)."""
    from certisigma import CertiSigmaClient, CertiSigmaError

    tag_filter = {"and": [{"key": k, "value": v} for k, v in filters]}

    kwargs: dict[str, Any] = {}
    if api_key:
        kwargs["api_key"] = api_key
    if base_url:
        kwargs["base_url"] = base_url
    client = CertiSigmaClient(**kwargs)
    try:
        try:
            result = client.query_tags(
                filter=tag_filter,
                limit=limit,
                cursor=cursor,
            )
            return TagQueryResult(
                attestation_ids=list(result.attestation_ids),
                next_cursor=result.next_cursor,
                count=result.count,
            )
        except CertiSigmaError as exc:
            logger.error("Tag query failed: %s", exc)
            return TagQueryResult(error=str(exc))
    finally:
        client.close()


def _resolve_encryption_key(explicit_key: str | None = None) -> str:
    """Get encryption key from argument or config."""
    from .config import resolve_encryption_key
    return resolve_encryption_key(explicit_key)
