package protosocket

import (
	"fmt"

	"google.golang.org/protobuf/types/known/structpb"
)

const ProtocolVersion = "nomadcode.proto-socket.v1"

type Envelope struct {
	ProtocolVersion string         `json:"protocol_version"`
	ID              string         `json:"id"`
	CorrelationID   string         `json:"correlation_id"`
	Type            string         `json:"type"`
	Channel         string         `json:"channel"`
	Action          string         `json:"action"`
	Actor           map[string]any `json:"actor,omitempty"`
	Auth            map[string]any `json:"auth,omitempty"`
	Payload         map[string]any `json:"payload,omitempty"`
	Error           *EnvelopeError `json:"error,omitempty"`
	Meta            map[string]any `json:"meta,omitempty"`
}

type EnvelopeError struct {
	Code      string         `json:"code"`
	Message   string         `json:"message"`
	Retryable bool           `json:"retryable"`
	Details   map[string]any `json:"details,omitempty"`
}

func (e Envelope) ToStruct() (*structpb.Struct, error) {
	m := map[string]any{
		"protocol_version": e.ProtocolVersion,
		"id":               e.ID,
		"correlation_id":   e.CorrelationID,
		"type":             e.Type,
		"channel":          e.Channel,
		"action":           e.Action,
	}
	if e.Actor != nil {
		m["actor"] = e.Actor
	}
	if e.Auth != nil {
		m["auth"] = e.Auth
	}
	if e.Payload != nil {
		m["payload"] = e.Payload
	}
	if e.Meta != nil {
		m["meta"] = e.Meta
	}
	if e.Error != nil {
		errMap := map[string]any{
			"code":      e.Error.Code,
			"message":   e.Error.Message,
			"retryable": e.Error.Retryable,
		}
		if e.Error.Details != nil {
			errMap["details"] = e.Error.Details
		}
		m["error"] = errMap
	}
	return structpb.NewStruct(m)
}

func EnvelopeFromStruct(s *structpb.Struct) (Envelope, error) {
	var env Envelope
	if s == nil {
		return env, fmt.Errorf("nil struct")
	}
	m := s.AsMap()

	if v, ok := m["protocol_version"].(string); ok {
		env.ProtocolVersion = v
	}
	if v, ok := m["id"].(string); ok {
		env.ID = v
	}
	if v, ok := m["correlation_id"].(string); ok {
		env.CorrelationID = v
	}
	if v, ok := m["type"].(string); ok {
		env.Type = v
	}
	if v, ok := m["channel"].(string); ok {
		env.Channel = v
	}
	if v, ok := m["action"].(string); ok {
		env.Action = v
	}
	if v, ok := m["actor"].(map[string]any); ok {
		env.Actor = v
	}
	if v, ok := m["auth"].(map[string]any); ok {
		env.Auth = v
	}
	if v, ok := m["payload"].(map[string]any); ok {
		env.Payload = v
	}
	if v, ok := m["meta"].(map[string]any); ok {
		env.Meta = v
	}
	if errVal, ok := m["error"].(map[string]any); ok {
		var envErr EnvelopeError
		if code, ok := errVal["code"].(string); ok {
			envErr.Code = code
		}
		if msg, ok := errVal["message"].(string); ok {
			envErr.Message = msg
		}
		if ret, ok := errVal["retryable"].(bool); ok {
			envErr.Retryable = ret
		}
		if details, ok := errVal["details"].(map[string]any); ok {
			envErr.Details = details
		}
		env.Error = &envErr
	}
	return env, nil
}
