"""Census Derived Lists — opaque third-party hash verification.

Uses HMAC-SHA256 derived lists from SDK v1.7.0 to enable third-party
breach detection without exposing the actual file inventory.

Workflow:
1. Owner creates a derived list from manifest hashes or tag filter
2. Owner shares ``list_id`` + ``list_key`` with the third party
3. Third party computes HMAC-SHA256 of their suspect hashes with ``list_key``
4. Third party submits derived hashes to ``match_derived_list()``
5. Owner reviews access log for audit trail

The server never sees plaintext hashes from either party.
"""

from __future__ import annotations

import hashlib
import hmac
import logging
import re
from dataclasses import dataclass, field
from typing import Any, Optional

logger = logging.getLogger(__name__)


@dataclass
class DerivedListCreateResult:
    success: bool
    list_id: Optional[str] = None
    list_key: Optional[str] = None
    item_count: int = 0
    content_hash: Optional[str] = None
    signature: Optional[str] = None
    label: Optional[str] = None
    expires_at: Optional[str] = None
    warning: Optional[str] = None
    error: Optional[str] = None


@dataclass
class DerivedListInfo:
    list_id: str
    item_count: int
    label: Optional[str]
    expires_at: Optional[str]
    revoked_at: Optional[str]
    created_at: Optional[str]
    active: bool


@dataclass
class DerivedListDetail:
    list_id: str
    item_count: int
    content_hash: Optional[str]
    label: Optional[str]
    tag_filter: Optional[dict] = None
    expires_at: Optional[str] = None
    revoked_at: Optional[str] = None
    created_at: Optional[str] = None
    active: bool = False
    match_count: int = 0


@dataclass
class MatchResult:
    list_id: str
    success: bool
    matched: int = 0
    unmatched: int = 0
    total: int = 0
    matches: list[str] = field(default_factory=list)
    error: Optional[str] = None


@dataclass
class SignatureResult:
    list_id: str
    success: bool
    signature: Optional[str] = None
    signing_key_id: Optional[str] = None
    content_hash: Optional[str] = None
    item_count: int = 0
    created_at: Optional[str] = None
    expires_at: Optional[str] = None
    error: Optional[str] = None


@dataclass
class AccessLogEntry:
    matched_count: int
    total_submitted: int
    ip_address: Optional[str]
    created_at: Optional[str]


def create_derived_list(
    *,
    hashes: list[str] | None = None,
    tag_filter: dict | None = None,
    label: str | None = None,
    expires_in_hours: int | None = None,
    api_key: str | None = None,
    base_url: str | None = None,
) -> DerivedListCreateResult:
    """Create an HMAC-SHA256 derived list for third-party matching."""
    from certisigma import CertiSigmaClient, CertiSigmaError

    if not hashes and not tag_filter:
        return DerivedListCreateResult(
            success=False,
            error="Provide either --hashes (from manifest) or --tag-filter.",
        )

    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.create_derived_list(
                hashes=hashes,
                tag_filter=tag_filter,
                label=label,
                expires_in_hours=expires_in_hours,
            )
            return DerivedListCreateResult(
                success=True,
                list_id=result.id,
                list_key=result.list_key,
                item_count=result.item_count,
                content_hash=result.content_hash,
                signature=result.signature,
                label=result.label,
                expires_at=result.expires_at,
                warning=result.warning,
            )
        except CertiSigmaError as exc:
            return DerivedListCreateResult(success=False, error=str(exc))
    finally:
        client.close()


def list_derived_lists(
    *,
    api_key: str | None = None,
    base_url: str | None = None,
) -> list[DerivedListInfo]:
    """List all derived lists owned by the authenticated user."""
    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:
            items = client.list_derived_lists()
        except CertiSigmaError as exc:
            logger.error("Failed to list derived lists: %s", exc)
            return []
        return [
            DerivedListInfo(
                list_id=i.id,
                item_count=i.item_count,
                label=i.label,
                expires_at=i.expires_at,
                revoked_at=i.revoked_at,
                created_at=i.created_at,
                active=i.active,
            )
            for i in items
        ]
    finally:
        client.close()


def get_derived_list_detail(
    list_id: str,
    *,
    api_key: str | None = None,
    base_url: str | None = None,
) -> DerivedListDetail | DerivedListCreateResult:
    """Get details about a specific derived list."""
    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:
            d = client.get_derived_list(list_id)
            return DerivedListDetail(
                list_id=d.id,
                item_count=d.item_count,
                content_hash=d.content_hash,
                label=d.label,
                tag_filter=d.tag_filter,
                expires_at=d.expires_at,
                revoked_at=d.revoked_at,
                created_at=d.created_at,
                active=d.active,
                match_count=d.match_count,
            )
        except CertiSigmaError as exc:
            return DerivedListCreateResult(success=False, error=str(exc))
    finally:
        client.close()


def _validate_list_key(list_key: str) -> None:
    """Validate HMAC list key (64 hex chars).

    Uses strict regex — ``bytes.fromhex()`` silently strips whitespace,
    which would produce a sub-256-bit key.
    """
    if not re.fullmatch(r"[0-9a-fA-F]{64}", list_key):
        raise ValueError(
            f"list_key must be exactly 64 hex characters (got {len(list_key)})."
        )


def derive_hashes(plain_hashes: list[str], list_key: str) -> list[str]:
    """Compute HMAC-SHA256 of plain hashes using the list key.

    This is what a third party does before calling ``match_derived_list()``.
    """
    _validate_list_key(list_key)
    key_bytes = bytes.fromhex(list_key)
    return [
        hmac.new(key_bytes, h.encode("utf-8"), hashlib.sha256).hexdigest()
        for h in plain_hashes
    ]


def match_derived_list(
    list_id: str,
    list_key: str,
    plain_hashes: list[str],
    *,
    api_key: str | None = None,
    base_url: str | None = None,
) -> MatchResult:
    """Match suspect hashes against a derived list.

    Computes HMAC-SHA256 client-side, then submits derived hashes
    to the server for comparison.
    """
    from certisigma import CertiSigmaClient, CertiSigmaError

    if not plain_hashes:
        return MatchResult(
            list_id=list_id,
            success=False,
            error="No hashes provided for matching.",
        )

    try:
        derived = derive_hashes(plain_hashes, list_key)
    except ValueError as exc:
        return MatchResult(
            list_id=list_id,
            success=False,
            error=f"Invalid list_key: {exc}",
        )

    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.match_derived_list(
                list_id=list_id,
                list_key=list_key,
                hashes=derived,
            )
            return MatchResult(
                list_id=list_id,
                success=True,
                matched=result.matched,
                unmatched=result.unmatched,
                total=result.total,
                matches=[m.get("derived_hash", m) if isinstance(m, dict) else str(m)
                         for m in result.matches],
            )
        except CertiSigmaError as exc:
            return MatchResult(list_id=list_id, success=False, error=str(exc))
    finally:
        client.close()


def revoke_derived_list(
    list_id: str,
    *,
    api_key: str | None = None,
    base_url: str | None = None,
) -> DerivedListCreateResult:
    """Revoke a derived list."""
    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.revoke_derived_list(list_id)
            return DerivedListCreateResult(success=True, list_id=list_id)
        except CertiSigmaError as exc:
            return DerivedListCreateResult(success=False, list_id=list_id, error=str(exc))
    finally:
        client.close()


def get_signature(
    list_id: str,
    *,
    base_url: str | None = None,
) -> SignatureResult:
    """Get the ECDSA signature of a derived list (public, no auth).

    Third parties use this to cryptographically verify list integrity
    and provenance — proving the list was created by CertiSigma at a
    specific time and has not been tampered with.
    """
    from certisigma import CertiSigmaClient, CertiSigmaError

    kwargs: dict[str, Any] = {}
    if base_url:
        kwargs["base_url"] = base_url
    client = CertiSigmaClient(**kwargs)
    try:
        try:
            sig = client.get_derived_list_signature(list_id)
            return SignatureResult(
                list_id=sig.list_id,
                success=True,
                signature=sig.signature,
                signing_key_id=sig.signing_key_id,
                content_hash=sig.content_hash,
                item_count=sig.item_count,
                created_at=sig.created_at,
                expires_at=sig.expires_at,
            )
        except CertiSigmaError as exc:
            return SignatureResult(list_id=list_id, success=False, error=str(exc))
    finally:
        client.close()


def get_access_log(
    list_id: str,
    *,
    limit: int = 50,
    api_key: str | None = None,
    base_url: str | None = None,
) -> list[AccessLogEntry]:
    """Get the access log for a derived list (owner audit trail)."""
    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:
            entries = client.get_derived_list_access_log(list_id, limit=limit)
            return [
                AccessLogEntry(
                    matched_count=e.matched_count,
                    total_submitted=e.total_submitted,
                    ip_address=e.ip_address,
                    created_at=e.created_at,
                )
                for e in entries
            ]
        except CertiSigmaError as exc:
            logger.error("Access log fetch failed: %s", exc)
            return []
    finally:
        client.close()
