"""Identity context helpers for the Golden Envelope contract.

Flat headers are the authoritative source of tenant identity. These helpers
convert between IdentityContext protobuf structs and flat Kafka headers.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Optional

from neonlink.record import (
    HEADER_BATCH_ID,
    HEADER_CONNECTION_ID,
    HEADER_DATA_TYPE,
    HEADER_ENTITY_ID,
    HEADER_EVENT_ID,
    HEADER_IDEMPOTENCY_KEY,
    HEADER_JOB_ID,
    HEADER_PROVIDER,
    HEADER_REQUEST_ID,
    HEADER_TRIGGER_SOURCE,
    HEADER_USER_ID,
    Record,
)


def flat_headers_from_identity(identity) -> dict[str, str]:
    """Extract individual flat Kafka headers from an IdentityContext protobuf.

    Returns only non-empty fields as plain string key-value pairs.
    Used by RecordBuilder and outbox relay patterns.

    Args:
        identity: A ``messaging.v1.IdentityContext`` protobuf message, or None.

    Returns:
        dict of header key -> string value (empty fields omitted).
    """
    if identity is None:
        return {}

    headers: dict[str, str] = {}

    if v := _proto_str(identity, "user_id"):
        headers[HEADER_USER_ID] = v
    if v := _proto_str(identity, "entity_id"):
        headers[HEADER_ENTITY_ID] = v
    if v := _proto_str(identity, "connection_id"):
        headers[HEADER_CONNECTION_ID] = v
    if v := _proto_str(identity, "batch_id"):
        headers[HEADER_BATCH_ID] = v
    if v := _proto_str(identity, "provider"):
        headers[HEADER_PROVIDER] = v
    if v := _proto_str(identity, "request_id"):
        headers[HEADER_REQUEST_ID] = v
    if v := _proto_str(identity, "event_id"):
        headers[HEADER_EVENT_ID] = v
    if v := _proto_str(identity, "job_id"):
        headers[HEADER_JOB_ID] = v
    if v := _proto_str(identity, "trigger_source"):
        headers[HEADER_TRIGGER_SOURCE] = v
    if v := _proto_str(identity, "idempotency_key"):
        headers[HEADER_IDEMPOTENCY_KEY] = v

    # DataType enum: only set if non-zero (not DATA_TYPE_UNSPECIFIED).
    dt = getattr(identity, "data_type", 0)
    if dt:
        try:
            from messaging.v1 import messaging_pb2 as pb
            headers[HEADER_DATA_TYPE] = pb.DataType.Name(dt)
        except (ImportError, ValueError):
            headers[HEADER_DATA_TYPE] = str(dt)

    return headers


def identity_from_flat_headers(rec: Record):
    """Reconstruct an IdentityContext protobuf from flat Kafka headers.

    Inverse of ``flat_headers_from_identity``. Returns a new IdentityContext
    populated from the record's flat headers.

    Args:
        rec: A neonlink Record with flat identity headers.

    Returns:
        A ``messaging.v1.IdentityContext`` protobuf message.
    """
    from messaging.v1 import messaging_pb2 as pb

    identity = pb.IdentityContext()
    identity.user_id = rec.get_header(HEADER_USER_ID)
    identity.entity_id = rec.get_header(HEADER_ENTITY_ID)
    identity.connection_id = rec.get_header(HEADER_CONNECTION_ID)
    identity.batch_id = rec.get_header(HEADER_BATCH_ID)
    identity.provider = rec.get_header(HEADER_PROVIDER)
    identity.request_id = rec.get_header(HEADER_REQUEST_ID)
    identity.event_id = rec.get_header(HEADER_EVENT_ID)
    identity.job_id = rec.get_header(HEADER_JOB_ID)
    identity.trigger_source = rec.get_header(HEADER_TRIGGER_SOURCE)
    identity.idempotency_key = rec.get_header(HEADER_IDEMPOTENCY_KEY)

    raw_dt = rec.get_header(HEADER_DATA_TYPE)
    if raw_dt:
        try:
            identity.data_type = pb.DataType.Value(raw_dt)
        except ValueError:
            pass

    return identity


@dataclass
class IdentityRequirements:
    """Defines which fields are mandatory for a given publish context."""

    require_user_id: bool = False
    require_entity_id: bool = False
    require_batch_id: bool = False
    require_data_type: bool = False
    require_connection_id: bool = False
    require_provider: bool = False
    require_trigger_source: bool = False
    require_idempotency: bool = False


IDENTITY_REQUIRED_ETL = IdentityRequirements(
    require_user_id=True,
    require_entity_id=True,
    require_batch_id=True,
    require_data_type=True,
    require_connection_id=True,
    require_provider=True,
    require_trigger_source=True,
)

IDENTITY_REQUIRED_BACKFILL = IdentityRequirements(
    require_user_id=True,
    require_entity_id=True,
    require_data_type=True,
    require_connection_id=True,
    require_provider=True,
    require_trigger_source=True,
)

IDENTITY_REQUIRED_WEBHOOK_INGRESS = IdentityRequirements(
    require_connection_id=True,
    require_provider=True,
    require_trigger_source=True,
    require_idempotency=True,
)


def validate_identity_context(identity, req: IdentityRequirements) -> None:
    """Validate that required fields are present and non-empty.

    Raises:
        ValueError: If any required field is missing.
    """
    if identity is None:
        raise ValueError("identity context is None")

    missing: list[str] = []
    if req.require_user_id and not _proto_str(identity, "user_id"):
        missing.append("user_id")
    if req.require_entity_id and not _proto_str(identity, "entity_id"):
        missing.append("entity_id")
    if req.require_batch_id and not _proto_str(identity, "batch_id"):
        missing.append("batch_id")
    if req.require_data_type and not getattr(identity, "data_type", 0):
        missing.append("data_type")
    if req.require_connection_id and not _proto_str(identity, "connection_id"):
        missing.append("connection_id")
    if req.require_provider and not _proto_str(identity, "provider"):
        missing.append("provider")
    if req.require_trigger_source and not _proto_str(identity, "trigger_source"):
        missing.append("trigger_source")
    if req.require_idempotency and not _proto_str(identity, "idempotency_key"):
        missing.append("idempotency_key")

    if missing:
        raise ValueError(f"missing identity fields: {', '.join(missing)}")


def _proto_str(msg, field_name: str) -> str:
    """Safely get a string field from a protobuf message, stripped."""
    val = getattr(msg, field_name, "")
    return val.strip() if isinstance(val, str) else ""
