package uevent

import (
	"bytes"
	"strconv"
	"strings"
)

// A Message represents a kernel message to userspace.
type Message struct {
	// alloc_uevent_skb: action_string
	Action KobjectAction `json:"action"`
	// alloc_uevent_skb: devpath
	DevPath string `json:"devpath"`
	// add_uevent_var: key value strings
	Env []string `json:"env"`
}

// String returns a multiline user-facing string representation of [Message].
func (msg *Message) String() string {
	var buf strings.Builder
	buf.WriteString(msg.Action.String() + " event")
	if msg.DevPath != "" {
		buf.WriteString(" on " + msg.DevPath)
	}
	buf.WriteString(":")
	for _, s := range msg.Env {
		buf.WriteString("\n" + s)
	}
	return buf.String()
}

var (
	// zero is a single pre-allocated NUL character.
	zero = []byte{0}

	// sepHeader is the separator in a [Message] header.
	sepHeader = []byte{'@'}
)

func (msg *Message) AppendBinary(b []byte) (_ []byte, err error) {
	if b, err = msg.Action.AppendText(b); err != nil {
		return
	}
	b = append(b, sepHeader...)
	b = append(b, msg.DevPath...)
	b = append(b, zero...)
	for _, s := range msg.Env {
		b = append(b, s...)
		b = append(b, zero...)
	}
	return b, nil
}

func (msg *Message) MarshalBinary() ([]byte, error) {
	return msg.AppendBinary(nil)
}

// MissingHeaderError is an invalid representation of [Message] which is missing
// its header added by alloc_uevent_skb.
type MissingHeaderError string

var _ Recoverable = MissingHeaderError("")

func (MissingHeaderError) recoverable() {}
func (e MissingHeaderError) Error() string {
	return "message " + strconv.Quote(string(e)) + " has no header"
}

// MessageError describes a malformed representation of [Message].
type MessageError struct {
	// Full offending data.
	Data string `json:"data"`
	// Offending section.
	Section string `json:"section"`
	// Part of header in Section.
	Kind int `json:"kind"`
}

var _ Recoverable = new(MessageError)
var _ Nontrivial = new(MessageError)

const (
	// MErrorKindHeaderSep denotes a message header missing its separator.
	MErrorKindHeaderSep = iota
	// MErrorKindFinalNUL denotes a message body missing its final NUL terminator.
	MErrorKindFinalNUL
)

func (*MessageError) recoverable() {}
func (*MessageError) nontrivial()  {}
func (e *MessageError) Error() string {
	switch e.Kind {
	case MErrorKindHeaderSep:
		return "header " + strconv.Quote(e.Section) + " missing separator"
	case MErrorKindFinalNUL:
		return "entry " + strconv.Quote(e.Section) + " missing NUL"

	default:
		return "section " + strconv.Quote(e.Section) + " is invalid"
	}
}

func (msg *Message) UnmarshalBinary(data []byte) error {
	header, body, ok := bytes.Cut(data, zero)
	if !ok {
		return MissingHeaderError(data)
	}

	action_string, devpath, ok := bytes.Cut(header, sepHeader)
	if !ok {
		return &MessageError{string(data), string(header), MErrorKindHeaderSep}
	}

	if err := msg.Action.UnmarshalText(action_string); err != nil {
		return err
	}
	msg.DevPath = string(devpath)

	if len(body) == 0 {
		msg.Env = nil
		return nil
	}
	msg.Env = make([]string, 0, bytes.Count(body, zero))

	var s []byte
	for len(body) != 0 {
		var r []byte
		s, r, ok = bytes.Cut(body, zero)
		if !ok {
			return &MessageError{string(data), string(body), MErrorKindFinalNUL}
		}
		body = r
		msg.Env = append(msg.Env, string(s))
	}
	return nil
}
