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) {
	value := 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 {
		value["actor"] = e.Actor
	}
	if e.Auth != nil {
		value["auth"] = e.Auth
	}
	if e.Payload != nil {
		value["payload"] = e.Payload
	}
	if e.Meta != nil {
		value["meta"] = e.Meta
	}
	if e.Error != nil {
		errValue := map[string]any{
			"code":      e.Error.Code,
			"message":   e.Error.Message,
			"retryable": e.Error.Retryable,
		}
		if e.Error.Details != nil {
			errValue["details"] = e.Error.Details
		}
		value["error"] = errValue
	}
	return structpb.NewStruct(value)
}

func EnvelopeFromStruct(value *structpb.Struct) (Envelope, error) {
	var env Envelope
	if value == nil {
		return env, fmt.Errorf("nil struct")
	}
	raw := value.AsMap()
	if v, ok := raw["protocol_version"].(string); ok {
		env.ProtocolVersion = v
	}
	if v, ok := raw["id"].(string); ok {
		env.ID = v
	}
	if v, ok := raw["correlation_id"].(string); ok {
		env.CorrelationID = v
	}
	if v, ok := raw["type"].(string); ok {
		env.Type = v
	}
	if v, ok := raw["channel"].(string); ok {
		env.Channel = v
	}
	if v, ok := raw["action"].(string); ok {
		env.Action = v
	}
	if v, ok := raw["actor"].(map[string]any); ok {
		env.Actor = v
	}
	if v, ok := raw["auth"].(map[string]any); ok {
		env.Auth = v
	}
	if v, ok := raw["payload"].(map[string]any); ok {
		env.Payload = v
	}
	if v, ok := raw["meta"].(map[string]any); ok {
		env.Meta = v
	}
	if errValue, ok := raw["error"].(map[string]any); ok {
		envErr := EnvelopeError{}
		if v, ok := errValue["code"].(string); ok {
			envErr.Code = v
		}
		if v, ok := errValue["message"].(string); ok {
			envErr.Message = v
		}
		if v, ok := errValue["retryable"].(bool); ok {
			envErr.Retryable = v
		}
		if v, ok := errValue["details"].(map[string]any); ok {
			envErr.Details = v
		}
		env.Error = &envErr
	}
	return env, nil
}
