package models

import (
	"bytes"
	"database/sql/driver"
	"encoding/json"
	"errors"
	"fmt"
	"regexp"
)

// Annotations encapsulates key-value metadata associated with resource. The structure is immutable via its public API and nil-safe for its contract
// permissive nilability is here to simplify updates and reduce the need for nil handling in extensions - annotations should be updated by over-writing the original object:
//  target.Annotations  = target.Annotations.With("fooKey",1)
// old MD remains empty
// Annotations is lenable
type Annotations map[string]*annotationValue

// annotationValue encapsulates a value in the annotations map,
// This is stored in its compacted, un-parsed JSON format for later (re-) parsing into specific structs or values
// annotationValue objects are  immutable after JSON load
type annotationValue []byte

const (
	maxAnnotationValueBytes = 512
	maxAnnotationKeyBytes   = 128
	maxAnnotationsKeys      = 100
)

// Equals is defined based on un-ordered k/v comparison at of the annotation keys and (compacted) values of annotations, JSON object-value equality for values is property-order dependent
func (m Annotations) Equals(other Annotations) bool {
	if len(m) != len(other) {
		return false
	}
	return m.Subset(other)
}

func (m Annotations) Subset(other Annotations) bool {
	for k1, v1 := range m {
		v2, _ := other[k1]
		if v2 == nil {
			return false
		}
		if !bytes.Equal(*v1, *v2) {
			return false
		}
	}
	return true
}

func EmptyAnnotations() Annotations {
	return nil
}

func (mv *annotationValue) String() string {
	return string(*mv)
}

func (v *annotationValue) MarshalJSON() ([]byte, error) {
	return *v, nil
}

func (mv *annotationValue) isEmptyValue() bool {
	sval := string(*mv)
	return sval == "\"\"" || sval == "null"
}

// UnmarshalJSON compacts annotation values but does not alter key-ordering for keys
func (mv *annotationValue) UnmarshalJSON(val []byte) error {
	buf := bytes.Buffer{}
	err := json.Compact(&buf, val)
	if err != nil {
		return err
	}
	if err != nil {
		return err
	}
	*mv = buf.Bytes()
	return nil
}

var validKeyRegex = regexp.MustCompile("^[!-~]+$")

func validateField(key string, value annotationValue) APIError {

	if !validKeyRegex.Match([]byte(key)) {
		return ErrInvalidAnnotationKey
	}

	keyLen := len([]byte(key))

	if keyLen > maxAnnotationKeyBytes {
		return ErrInvalidAnnotationKeyLength
	}

	if value.isEmptyValue() {
		return ErrInvalidAnnotationValue
	}

	if len(value) > maxAnnotationValueBytes {
		return ErrInvalidAnnotationValueLength
	}

	return nil
}

// With Creates a new annotations object containing the specified value - this does not perform size checks on the total number of keys
// this validates the correctness of the key and value. this returns a new the annotations object with the key set.
func (m Annotations) With(key string, data interface{}) (Annotations, error) {

	if data == nil || data == "" {
		return nil, errors.New("empty annotation value")
	}

	jsonBytes, err := json.Marshal(data)
	if err != nil {
		return nil, err
	}

	newVal := jsonBytes
	err = validateField(key, newVal)

	if err != nil {
		return nil, err
	}

	var newMd Annotations
	if m == nil {
		newMd = make(Annotations, 1)
	} else {
		newMd = m.clone()
	}
	mv := annotationValue(newVal)
	newMd[key] = &mv
	return newMd, nil
}

// Validate validates a final annotations object prior to store,
// This will reject partial/patch changes with empty values (containing deletes)
func (m Annotations) Validate() APIError {

	for k, v := range m {
		err := validateField(k, *v)
		if err != nil {
			return err
		}
	}

	if len(m) > maxAnnotationsKeys {
		return ErrTooManyAnnotationKeys
	}
	return nil
}

// Get returns a raw JSON value of a annotation key
func (m Annotations) Get(key string) ([]byte, bool) {
	if v, ok := m[key]; ok {
		return *v, ok
	}
	return nil, false
}

// GetString returns a string value if the annotation value is a string, otherwise an error
func (m Annotations) GetString(key string) (string, error) {
	if v, ok := m[key]; ok {
		var s string
		if err := json.Unmarshal([]byte(*v), &s); err != nil {
			return "", err
		}
		return s, nil
	}
	return "", errors.New("Annotation not found")
}

// Without returns a new annotations object with a value excluded
func (m Annotations) Without(key string) Annotations {
	nuVal := m.clone()
	delete(nuVal, key)
	return nuVal
}

// MergeChange merges a delta (possibly including deletes) with an existing annotations object and returns a new (copy) annotations object or an error.
// This assumes that both old and new annotations objects contain only valid keys and only newVs may contain  deletes
func (m Annotations) MergeChange(newVs Annotations) Annotations {
	newMd := m.clone()

	for k, v := range newVs {
		if v.isEmptyValue() {
			delete(newMd, k)
		} else {
			if newMd == nil {
				newMd = make(Annotations)
			}
			newMd[k] = v
		}
	}

	if len(newMd) == 0 {
		return EmptyAnnotations()
	}
	return newMd
}

// clone produces a key-wise copy of the underlying annotations
// publically MD can be copied by reference as it's (by contract) immutable
func (m Annotations) clone() Annotations {

	if m == nil {
		return nil
	}
	newMd := make(Annotations, len(m))
	for ok, ov := range m {
		newMd[ok] = ov
	}
	return newMd
}

// Value implements sql.Valuer, returning a string
func (m Annotations) Value() (driver.Value, error) {
	if len(m) < 1 {
		return driver.Value(string("")), nil
	}
	var b bytes.Buffer
	err := json.NewEncoder(&b).Encode(m)
	return driver.Value(b.String()), err
}

// Scan implements sql.Scanner
func (m *Annotations) Scan(value interface{}) error {
	if value == nil || value == "" {
		*m = nil
		return nil
	}
	bv, err := driver.String.ConvertValue(value)
	if err == nil {
		var b []byte
		switch x := bv.(type) {
		case []byte:
			b = x
		case string:
			b = []byte(x)
		}

		if len(b) > 0 {
			return json.Unmarshal(b, m)
		}

		*m = nil
		return nil
	}

	// otherwise, return an error
	return fmt.Errorf("annotations invalid db format: %T %T value, err: %v", value, bv, err)
}
