package nip77

import (
	"bytes"
	"fmt"

	"github.com/mailru/easyjson"
	jwriter "github.com/mailru/easyjson/jwriter"
	"github.com/nbd-wtf/go-nostr"
	"github.com/tidwall/gjson"
)

func ParseNegMessage(message []byte) nostr.Envelope {
	firstComma := bytes.Index(message, []byte{','})
	if firstComma == -1 {
		return nil
	}
	label := message[0:firstComma]

	var v nostr.Envelope
	switch {
	case bytes.Contains(label, []byte("NEG-MSG")):
		v = &MessageEnvelope{}
	case bytes.Contains(label, []byte("NEG-OPEN")):
		v = &OpenEnvelope{}
	case bytes.Contains(label, []byte("NEG-ERR")):
		v = &ErrorEnvelope{}
	case bytes.Contains(label, []byte("NEG-CLOSE")):
		v = &CloseEnvelope{}
	default:
		return nil
	}

	if err := v.UnmarshalJSON(message); err != nil {
		return nil
	}
	return v
}

var (
	_ nostr.Envelope = (*OpenEnvelope)(nil)
	_ nostr.Envelope = (*MessageEnvelope)(nil)
	_ nostr.Envelope = (*CloseEnvelope)(nil)
	_ nostr.Envelope = (*ErrorEnvelope)(nil)
)

type OpenEnvelope struct {
	SubscriptionID string
	Filter         nostr.Filter
	Message        string
}

func (_ OpenEnvelope) Label() string { return "NEG-OPEN" }
func (v OpenEnvelope) String() string {
	b, _ := v.MarshalJSON()
	return string(b)
}

func (v *OpenEnvelope) UnmarshalJSON(data []byte) error {
	r := gjson.ParseBytes(data)
	arr := r.Array()
	if len(arr) != 4 {
		return fmt.Errorf("failed to decode NEG-OPEN envelope")
	}

	v.SubscriptionID = arr[1].Str
	v.Message = arr[3].Str
	return easyjson.Unmarshal([]byte(arr[2].Raw), &v.Filter)
}

func (v OpenEnvelope) MarshalJSON() ([]byte, error) {
	res := bytes.NewBuffer(make([]byte, 0, 17+len(v.SubscriptionID)+len(v.Message)+500))

	res.WriteString(`["NEG-OPEN","`)
	res.WriteString(v.SubscriptionID)
	res.WriteString(`",`)

	w := jwriter.Writer{NoEscapeHTML: true}
	v.Filter.MarshalEasyJSON(&w)
	w.Buffer.DumpTo(res)

	res.WriteString(`,"`)
	res.WriteString(v.Message)
	res.WriteString(`"]`)

	return res.Bytes(), nil
}

type MessageEnvelope struct {
	SubscriptionID string
	Message        string
}

func (_ MessageEnvelope) Label() string { return "NEG-MSG" }
func (v MessageEnvelope) String() string {
	b, _ := v.MarshalJSON()
	return string(b)
}

func (v *MessageEnvelope) UnmarshalJSON(data []byte) error {
	r := gjson.ParseBytes(data)
	arr := r.Array()
	if len(arr) < 3 {
		return fmt.Errorf("failed to decode NEG-MSG envelope")
	}
	v.SubscriptionID = arr[1].Str
	v.Message = arr[2].Str
	return nil
}

func (v MessageEnvelope) MarshalJSON() ([]byte, error) {
	res := bytes.NewBuffer(make([]byte, 0, 17+len(v.SubscriptionID)+len(v.Message)))

	res.WriteString(`["NEG-MSG","`)
	res.WriteString(v.SubscriptionID)
	res.WriteString(`","`)
	res.WriteString(v.Message)
	res.WriteString(`"]`)

	return res.Bytes(), nil
}

type CloseEnvelope struct {
	SubscriptionID string
}

func (_ CloseEnvelope) Label() string { return "NEG-CLOSE" }
func (v CloseEnvelope) String() string {
	b, _ := v.MarshalJSON()
	return string(b)
}

func (v *CloseEnvelope) UnmarshalJSON(data []byte) error {
	r := gjson.ParseBytes(data)
	arr := r.Array()
	if len(arr) < 2 {
		return fmt.Errorf("failed to decode NEG-CLOSE envelope")
	}
	v.SubscriptionID = arr[1].Str
	return nil
}

func (v CloseEnvelope) MarshalJSON() ([]byte, error) {
	res := bytes.NewBuffer(make([]byte, 0, 14+len(v.SubscriptionID)))
	res.WriteString(`["NEG-CLOSE","`)
	res.WriteString(v.SubscriptionID)
	res.WriteString(`"]`)
	return res.Bytes(), nil
}

type ErrorEnvelope struct {
	SubscriptionID string
	Reason         string
}

func (_ ErrorEnvelope) Label() string { return "NEG-ERROR" }
func (v ErrorEnvelope) String() string {
	b, _ := v.MarshalJSON()
	return string(b)
}

func (v *ErrorEnvelope) UnmarshalJSON(data []byte) error {
	r := gjson.ParseBytes(data)
	arr := r.Array()
	if len(arr) < 3 {
		return fmt.Errorf("failed to decode NEG-ERROR envelope")
	}
	v.SubscriptionID = arr[1].Str
	v.Reason = arr[2].Str
	return nil
}

func (v ErrorEnvelope) MarshalJSON() ([]byte, error) {
	res := bytes.NewBuffer(make([]byte, 0, 19+len(v.SubscriptionID)+len(v.Reason)))
	res.WriteString(`["NEG-ERROR","`)
	res.WriteString(v.SubscriptionID)
	res.WriteString(`","`)
	res.WriteString(v.Reason)
	res.WriteString(`"]`)
	return res.Bytes(), nil
}