// Copyright (C) 2023 Marius Schellenberger

package schema

import (
	"cmp"
	"encoding/hex"
	"errors"
	"fmt"
	"slices"

	"git.giftfish.de/ston1th/keyctl/pkg/api/types"
	"git.giftfish.de/ston1th/keyctl/pkg/core"
)

const (
	Version = "v1"
	KeyName = "key"
	ReqName = "req"
	KeyPath = "/" + Version + "/" + KeyName
	ReqPath = "/" + Version + "/" + ReqName
)

type NewKey struct {
	Name     string        `json:"name"`
	Existing string        `json:"existing,omitempty"`
	Size     int           `json:"size"`
	Encoding core.Encoding `json:"encoding"`
	Type     core.Type     `json:"type"`
}

var (
	ErrEmptyKeyName        = errors.New("empty key name")
	ErrExistingKeyType     = errors.New("invalid key type for existing key")
	ErrExistingKeyEncoding = errors.New("invalid key encoding for existing key")
	ErrInvalidKeyName      = errors.New("invalid key name")
	ErrInvalidKeySize      = errors.New("invalid key size")
)

const (
	MinKeySize = 1
	MaxKeySize = 1024
)

func (nk NewKey) Validate() error {
	if nk.Name == "" {
		return ErrEmptyKeyName
	}
	if nk.Existing != "" {
		if nk.Type == core.Xor {
			return ErrExistingKeyType
		}
		_, err := hex.DecodeString(nk.Existing)
		if err != nil {
			return ErrExistingKeyEncoding
		}
	}
	if core.NameRe.MatchString(nk.Name) {
		return ErrInvalidKeyName
	}
	if nk.Size > MaxKeySize || nk.Size < MinKeySize {
		return ErrInvalidKeySize
	}
	return nil
}

func ApproveURL(id, aid string) string {
	return KeyPath + "/" + id + "/" + aid
}

type Key struct {
	ID       string        `json:"id"`
	Name     string        `json:"name"`
	Size     int           `json:"size"`
	Encoding core.Encoding `json:"encoding"`
	Type     core.Type     `json:"type"`
	Key      string        `json:"key,omitempty"`
}

func (k Key) Print(share string) (string, error) {
	switch k.Type {
	case core.Shamir:
		part1, err := k.Encoding.Decode(k.Key)
		if err != nil {
			return "", err
		}
		part2, err := hex.DecodeString(share)
		if err != nil {
			return "", err
		}
		dec, err := k.Type.Decode(append(part1, part2...))
		if err != nil {
			return "", err
		}
		return k.Encoding.EncodeToString(dec), nil
	}
	return k.Key, nil
}

type Keys []Key

func (Keys) Less(a, b Key) int { return cmp.Compare(a.Name, b.Name) }
func (k Keys) Sort()           { slices.SortStableFunc(k, k.Less) }
func (k Keys) Len() int        { return len(k) }
func (k Keys) Fields() string {
	return "Key ID\t Name\t Size\t Encoding\t Type"
}
func (k Keys) Values() []string {
	vals := make([]string, len(k))
	for i, v := range k {
		vals[i] = fmt.Sprintf("%s\t %s\t %d\t %s\t %s",
			v.ID,
			v.Name,
			v.Size,
			v.Encoding,
			v.Type,
		)
	}
	return vals
}

func NewKeys(keys core.Keys) (k Keys) {
	for _, key := range keys {
		k = append(k, Key(key))
	}
	k.Sort()
	return
}

type Approval struct {
	ID     string       `json:"id"`
	AID    string       `json:"aid"`
	Name   string       `json:"name"`
	IP     string       `json:"ip"`
	Status types.Status `json:"status"`
}

type Approvals []Approval

func (Approvals) Less(a, b Approval) int { return cmp.Compare(a.Name, b.Name) }
func (a Approvals) Sort()                { slices.SortStableFunc(a, a.Less) }
func (a Approvals) Len() int             { return len(a) }
func (a Approvals) Fields() string {
	return "Key ID\t Approval ID\t Name\t Status\t IP"
}
func (a Approvals) Values() []string {
	vals := make([]string, len(a))
	for i, v := range a {
		vals[i] = fmt.Sprintf("%s\t %s\t %s\t %s\t %s",
			v.ID,
			v.AID,
			v.Name,
			v.Status,
			v.IP,
		)
	}
	return vals
}

func NewApprovals(m types.ApprovalMap) (a Approvals) {
	for k, v := range m {
		a = append(a, Approval{
			ID:     v.ID,
			AID:    k,
			Name:   v.Name,
			IP:     v.IP,
			Status: v.Status,
		})
	}
	a.Sort()
	return
}
