"""Consumer-side header parser for the Golden Envelope contract.

Reads flat headers directly -- no blob decoding on the primary path.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Optional

from neonlink.errors import PermanentError
from neonlink.record import (
    HEADER_BATCH_ID,
    HEADER_CONNECTION_ID,
    HEADER_CORRELATION_ID,
    HEADER_DATA_TYPE,
    HEADER_ENTITY_ID,
    HEADER_EVENT_ID,
    HEADER_IDEMPOTENCY_KEY,
    HEADER_JOB_ID,
    HEADER_MESSAGE_ID,
    HEADER_MESSAGE_TYPE,
    HEADER_PROVIDER,
    HEADER_REQUEST_ID,
    HEADER_RETRY_COUNT,
    HEADER_SOURCE_SERVICE,
    HEADER_TARGET_SERVICE,
    HEADER_TRIGGER_SOURCE,
    HEADER_USER_ID,
    Record,
)


@dataclass
class StandardHeaders:
    """All parsed Golden Envelope fields.

    Identity fields are populated from flat headers -- no blob decoding needed.
    """

    # Routing envelope.
    message_type: int = 0
    message_type_name: str = ""
    message_id: str = ""
    correlation_id: str = ""
    source_service: int = 0
    target_service: int = 0
    retry_count: int = 0

    # Identity fields (from flat headers -- authoritative).
    user_id: str = ""
    entity_id: str = ""
    connection_id: str = ""
    batch_id: str = ""
    provider: str = ""
    data_type: str = ""
    request_id: str = ""
    event_id: str = ""
    job_id: str = ""
    trigger_source: str = ""
    idempotency_key: str = ""

    def as_identity_context(self):
        """Reconstruct a typed IdentityContext protobuf from flat header values."""
        from messaging.v1 import messaging_pb2 as pb

        ic = pb.IdentityContext()
        ic.user_id = self.user_id
        ic.entity_id = self.entity_id
        ic.connection_id = self.connection_id
        ic.batch_id = self.batch_id
        ic.provider = self.provider
        ic.request_id = self.request_id
        ic.event_id = self.event_id
        ic.job_id = self.job_id
        ic.trigger_source = self.trigger_source
        ic.idempotency_key = self.idempotency_key
        if self.data_type:
            try:
                ic.data_type = pb.DataType.Value(self.data_type)
            except ValueError:
                pass
        return ic


def parse_headers(rec: Record) -> StandardHeaders:
    """Extract and validate all Golden Envelope headers from a Record.

    Reads flat headers directly -- no blob decoding, no deserialization.

    Raises:
        PermanentError: If mandatory headers (message_type, message_id,
            user_id, entity_id) are missing or empty.
    """
    if rec is None:
        raise PermanentError("record is None")

    raw_mt = rec.get_header(HEADER_MESSAGE_TYPE).strip()
    if not raw_mt:
        raise PermanentError(f"header {HEADER_MESSAGE_TYPE} is required")

    message_id = rec.get_header(HEADER_MESSAGE_ID).strip()
    if not message_id:
        raise PermanentError(f"header {HEADER_MESSAGE_ID} is required")

    user_id = rec.get_header(HEADER_USER_ID).strip()
    if not user_id:
        raise PermanentError(f"header {HEADER_USER_ID} is required")

    entity_id = rec.get_header(HEADER_ENTITY_ID).strip()
    if not entity_id:
        raise PermanentError(f"header {HEADER_ENTITY_ID} is required")

    # Parse enum values.
    message_type = 0
    source_service = 0
    target_service = 0
    try:
        from messaging.v1 import messaging_pb2 as pb
        message_type = pb.MessageType.Value(raw_mt)
        raw_ss = rec.get_header(HEADER_SOURCE_SERVICE).strip()
        if raw_ss:
            source_service = pb.SourceService.Value(raw_ss)
        raw_ts = rec.get_header(HEADER_TARGET_SERVICE).strip()
        if raw_ts:
            target_service = pb.TargetService.Value(raw_ts)
    except (ImportError, ValueError):
        pass

    retry_count = 0
    raw_rc = rec.get_header(HEADER_RETRY_COUNT).strip()
    if raw_rc:
        try:
            retry_count = int(raw_rc)
        except ValueError:
            pass

    return StandardHeaders(
        message_type=message_type,
        message_type_name=raw_mt,
        message_id=message_id,
        correlation_id=rec.get_header(HEADER_CORRELATION_ID).strip(),
        source_service=source_service,
        target_service=target_service,
        retry_count=retry_count,
        user_id=user_id,
        entity_id=entity_id,
        connection_id=rec.get_header(HEADER_CONNECTION_ID).strip(),
        batch_id=rec.get_header(HEADER_BATCH_ID).strip(),
        provider=rec.get_header(HEADER_PROVIDER).strip(),
        data_type=rec.get_header(HEADER_DATA_TYPE).strip(),
        request_id=rec.get_header(HEADER_REQUEST_ID).strip(),
        event_id=rec.get_header(HEADER_EVENT_ID).strip(),
        job_id=rec.get_header(HEADER_JOB_ID).strip(),
        trigger_source=rec.get_header(HEADER_TRIGGER_SOURCE).strip(),
        idempotency_key=rec.get_header(HEADER_IDEMPOTENCY_KEY).strip(),
    )


def parse_payload(rec: Record):
    """Unmarshal the JobDispatch protobuf from Record.value.

    Raises:
        PermanentError: If the record is None, value is empty, or
            protobuf deserialization fails.
    """
    if rec is None:
        raise PermanentError("record is None")
    if not rec.value:
        raise PermanentError("record value is empty")

    from messaging.v1 import messaging_pb2 as pb

    dispatch = pb.JobDispatch()
    try:
        dispatch.ParseFromString(rec.value)
    except Exception as e:
        raise PermanentError(f"malformed protobuf: {e}") from e
    return dispatch


def require_uuid(value: str, field_name: str) -> str:
    """Validate that a string value is non-empty.

    Raises:
        PermanentError: If the value is empty or blank.
    """
    v = value.strip() if value else ""
    if not v:
        raise PermanentError(f"{field_name} is required")
    return v
