"""RecordBuilder -- publisher-side enforcer for the Golden Envelope contract.

Services construct records ONLY through this builder. It guarantees:
- message_type and message_id are always present
- user_id and entity_id flat headers are always present (unless partial_identity)
- All non-empty IdentityContext fields are set as flat headers
- Payload is valid serialized protobuf
"""

from __future__ import annotations

import uuid
from typing import Optional

from neonlink.identity import flat_headers_from_identity
from neonlink.record import (
    HEADER_CORRELATION_ID,
    HEADER_MESSAGE_ID,
    HEADER_MESSAGE_TYPE,
    HEADER_SOURCE_SERVICE,
    HEADER_TARGET_SERVICE,
    Record,
)


class RecordBuilder:
    """Fluent builder for publish-ready Records with Golden Envelope headers."""

    def __init__(self, topic: str) -> None:
        self._topic = topic.strip()
        self._message_type: Optional[int] = None
        self._source: Optional[int] = None
        self._target: Optional[int] = None
        self._identity = None
        self._payload = None
        self._correlation_id: str = ""
        self._partition_key: Optional[bytes] = None
        self._extra_headers: dict[str, str] = {}
        self._message_id_override: str = ""
        self._partial_identity: bool = False

    def message_type(self, mt: int) -> RecordBuilder:
        """Set the message type discriminator (required)."""
        self._message_type = mt
        return self

    def source(self, s: int) -> RecordBuilder:
        """Set the publisher's service identity (required)."""
        self._source = s
        return self

    def target(self, t: int) -> RecordBuilder:
        """Set directed target service (optional, for request/response patterns)."""
        self._target = t
        return self

    def identity(self, ic) -> RecordBuilder:
        """Set the IdentityContext protobuf (required)."""
        self._identity = ic
        return self

    def partial_identity(self) -> RecordBuilder:
        """Allow publishing without user_id/entity_id (internal messages only)."""
        self._partial_identity = True
        return self

    def payload(self, jd) -> RecordBuilder:
        """Set the JobDispatch protobuf payload (required)."""
        self._payload = jd
        return self

    def correlation_id(self, cid: str) -> RecordBuilder:
        """Set the end-to-end correlation identifier."""
        self._correlation_id = cid.strip()
        return self

    def partition_key(self, key: bytes) -> RecordBuilder:
        """Set the Kafka partition key. Defaults to entity_id if not set."""
        self._partition_key = key
        return self

    def override_message_id(self, mid: str) -> RecordBuilder:
        """Set a specific message_id (outbox replay only)."""
        self._message_id_override = mid.strip()
        return self

    def header(self, key: str, value: str) -> RecordBuilder:
        """Add an extra application-specific header."""
        key = key.strip()
        if key:
            self._extra_headers[key] = value
        return self

    def build(self) -> Record:
        """Validate and construct the Record.

        Raises:
            ValueError: If required fields are missing.
        """
        from messaging.v1 import messaging_pb2 as pb

        if not self._topic:
            raise ValueError("neonlink: record builder: topic is required")
        if self._message_type is None or self._message_type == 0:
            raise ValueError("neonlink: record builder: message type is required")
        if self._source is None or self._source == 0:
            raise ValueError("neonlink: record builder: source service is required")
        if self._identity is None:
            raise ValueError("neonlink: record builder: identity context is required")

        user_id = getattr(self._identity, "user_id", "").strip()
        entity_id = getattr(self._identity, "entity_id", "").strip()
        if not self._partial_identity:
            if not user_id:
                raise ValueError("neonlink: record builder: identity.user_id is required")
            if not entity_id:
                raise ValueError("neonlink: record builder: identity.entity_id is required")

        if self._payload is None:
            raise ValueError("neonlink: record builder: payload is required")

        # Serialize payload.
        value = self._payload.SerializeToString()

        # Generate or use provided message_id.
        message_id = self._message_id_override or str(uuid.uuid4())

        # Build headers.
        headers: dict[str, str] = {}

        # Routing headers.
        headers[HEADER_MESSAGE_TYPE] = pb.MessageType.Name(self._message_type)
        headers[HEADER_MESSAGE_ID] = message_id
        headers[HEADER_SOURCE_SERVICE] = pb.SourceService.Name(self._source)
        if self._correlation_id:
            headers[HEADER_CORRELATION_ID] = self._correlation_id
        if self._target and self._target != 0:
            headers[HEADER_TARGET_SERVICE] = pb.TargetService.Name(self._target)

        # Flat identity headers (THE SOURCE OF TRUTH).
        headers.update(flat_headers_from_identity(self._identity))

        # Extra application-specific headers.
        headers.update(self._extra_headers)

        # Partition key defaults to entity_id.
        key = self._partition_key
        if key is None and entity_id:
            key = entity_id.encode("utf-8")

        return Record(
            topic=self._topic,
            key=key,
            value=value,
            headers=headers,
        )


def new_record(topic: str) -> RecordBuilder:
    """Start building a record for the given topic."""
    return RecordBuilder(topic)
