package spec

import (
	"strings"

	"github.com/cloudevents/sdk-go/v2/event"
)

// Version provides meta-data for a single spec-version.
type Version interface {
	// String name of the version, e.g. "1.0"
	String() string
	// Prefix for attribute names.
	Prefix() string
	// Attribute looks up a prefixed attribute name (case insensitive).
	// Returns nil if not found.
	Attribute(prefixedName string) Attribute
	// Attribute looks up the attribute from kind.
	// Returns nil if not found.
	AttributeFromKind(kind Kind) Attribute
	// Attributes returns all the context attributes for this version.
	Attributes() []Attribute
	// Convert translates a context to this version.
	Convert(event.EventContextConverter) event.EventContext
	// NewContext returns a new context for this version.
	NewContext() event.EventContext
	// SetAttribute sets named attribute to value.
	//
	// Name is case insensitive.
	// Does nothing if name does not start with prefix.
	SetAttribute(context event.EventContextWriter, name string, value interface{}) error
}

// Versions contains all known versions with the same attribute prefix.
type Versions struct {
	prefix string
	all    []Version
	m      map[string]Version
}

// Versions returns the list of all known versions, most recent first.
func (vs *Versions) Versions() []Version { return vs.all }

// Version returns the named version.
func (vs *Versions) Version(name string) Version {
	return vs.m[name]
}

// Latest returns the latest Version
func (vs *Versions) Latest() Version { return vs.all[0] }

// PrefixedSpecVersionName returns the specversion attribute PrefixedName
func (vs *Versions) PrefixedSpecVersionName() string { return vs.prefix + "specversion" }

// Prefix is the lowercase attribute name prefix.
func (vs *Versions) Prefix() string { return vs.prefix }

type attribute struct {
	accessor
	name    string
	version Version
}

func (a *attribute) PrefixedName() string { return a.version.Prefix() + a.name }
func (a *attribute) Name() string         { return a.name }
func (a *attribute) Version() Version     { return a.version }

type version struct {
	prefix  string
	context event.EventContext
	convert func(event.EventContextConverter) event.EventContext
	attrMap map[string]Attribute
	attrs   []Attribute
}

func (v *version) Attribute(name string) Attribute { return v.attrMap[strings.ToLower(name)] }
func (v *version) Attributes() []Attribute         { return v.attrs }
func (v *version) String() string                  { return v.context.GetSpecVersion() }
func (v *version) Prefix() string                  { return v.prefix }
func (v *version) NewContext() event.EventContext  { return v.context.Clone() }

// HasPrefix is a case-insensitive prefix check.
func (v *version) HasPrefix(name string) bool {
	return strings.HasPrefix(strings.ToLower(name), v.prefix)
}

func (v *version) Convert(c event.EventContextConverter) event.EventContext { return v.convert(c) }

func (v *version) SetAttribute(c event.EventContextWriter, name string, value interface{}) error {
	if a := v.Attribute(name); a != nil { // Standard attribute
		return a.Set(c, value)
	}
	name = strings.ToLower(name)
	var err error
	if v.HasPrefix(name) { // Extension attribute
		return c.SetExtension(strings.TrimPrefix(name, v.prefix), value)
	}
	return err
}

func (v *version) AttributeFromKind(kind Kind) Attribute {
	for _, a := range v.Attributes() {
		if a.Kind() == kind {
			return a
		}
	}
	return nil
}

func newVersion(
	prefix string,
	context event.EventContext,
	convert func(event.EventContextConverter) event.EventContext,
	attrs ...*attribute,
) *version {
	v := &version{
		prefix:  strings.ToLower(prefix),
		context: context,
		convert: convert,
		attrMap: map[string]Attribute{},
		attrs:   make([]Attribute, len(attrs)),
	}
	for i, a := range attrs {
		a.version = v
		v.attrs[i] = a
		v.attrMap[strings.ToLower(a.PrefixedName())] = a
	}
	return v
}

// WithPrefix returns a set of versions with prefix added to all attribute names.
func WithPrefix(prefix string) *Versions {
	attr := func(name string, kind Kind) *attribute {
		return &attribute{accessor: acc[kind], name: name}
	}
	vs := &Versions{
		m:      map[string]Version{},
		prefix: prefix,
		all: []Version{
			newVersion(prefix, event.EventContextV1{}.AsV1(),
				func(c event.EventContextConverter) event.EventContext { return c.AsV1() },
				attr("id", ID),
				attr("source", Source),
				attr("specversion", SpecVersion),
				attr("type", Type),
				attr("datacontenttype", DataContentType),
				attr("dataschema", DataSchema),
				attr("subject", Subject),
				attr("time", Time),
			),
			newVersion(prefix, event.EventContextV03{}.AsV03(),
				func(c event.EventContextConverter) event.EventContext { return c.AsV03() },
				attr("specversion", SpecVersion),
				attr("type", Type),
				attr("source", Source),
				attr("schemaurl", DataSchema),
				attr("subject", Subject),
				attr("id", ID),
				attr("time", Time),
				attr("datacontenttype", DataContentType),
			),
		},
	}
	for _, v := range vs.all {
		vs.m[v.String()] = v
	}
	return vs
}

// New returns a set of versions
func New() *Versions { return WithPrefix("") }

// Built-in un-prefixed versions.
var (
	VS  *Versions
	V03 Version
	V1  Version
)

func init() {
	VS = New()
	V03 = VS.Version("0.3")
	V1 = VS.Version("1.0")
}
