package sdf

import (
	"crypto/cipher"
	"fmt"
	"math/big"
	"strconv"
	"strings"
	"time"

	"xdx.jelly/xgcl/pbkd"
	"xdx.jelly/xgcl/sm/sm2"
	"xdx.jelly/xgcl/sm/sm4"
)

/*
Example of snapshot:
{
	"keys":[
		{
			"type": "Sm2SignKey",
			"url": "sm2sign/12",
			"material": ["base64(d)", "base64(04||x||y)"]
		},
		{
			"type": "Sm2SignKey",
			"url": "sm2enc/12",
			"material": ["base64(d)", "base64(04||x||y)"]
		},
		{
			"type": "BlockKey",
			"url": "kek/0",
			"material": ["base64(key)"]
		}
	]
}

*/
type KeyMaterial struct {
	Type      string   `json:"type"`
	Url       string   `json:"url"`
	Nonces    [][]byte `json:"nonces"` // coresponding to each Material, with add Url
	Materials [][]byte `json:"materials"`
}

type EncParam struct {
	Algo  string `json:"algo"` // only "sm4_gcm" are supported
	Salt  []byte `json:"salt"`
	Count uint32 `json:"count"`
}

type Snapshot struct {
	Time     time.Time      `json:"time"`
	Info     string         `json:"info"`
	Enc      bool           `json:"enc"`
	EncParam *EncParam      `json:"encParam"`
	Keys     []*KeyMaterial `json:"keys"`
}

func (s *SdfNoLock) Snapshot(enc bool, password []byte, count int) (*Snapshot, error) {

	snapshot := &Snapshot{
		Time: time.Now(),
		Enc:  enc,
		Keys: make([]*KeyMaterial, 0),
	}
	var aead cipher.AEAD
	var nonces [][]byte

	if enc {
		var salt []byte
		var err error
		var n uint32
		salt = make([]byte, 16)
		if n, err = s.SDF_GenerateRandom(salt); err != nil || n != uint32(len(salt)) {
			return nil, err
		}
		var encryptionKey []byte
		if encryptionKey, err = pbkd.PbkdfWithHmacSm3(password, salt, count, sm4.BlockSize); err != nil {
			return nil, err
		}

		if aead, err = sm4.NewGCM(encryptionKey); err != nil {
			return nil, err
		}

		nonces = [][]byte{
			make([]byte, aead.NonceSize()),
			make([]byte, aead.NonceSize()),
		}

		snapshot.EncParam = &EncParam{
			Algo:  "sm4_gcm",
			Salt:  salt,
			Count: uint32(count),
		}
	}

	for k, v := range s.Sm2Key {
		if v.signKey != nil {
			sk := v.signKey.Bytes()
			pk := v.signKey.PublicKey.Bytes()
			url := fmt.Sprintf("sm2sign/%d", k)
			thisNonces := nonces
			if aead != nil {
				s.SDF_GenerateRandom(nonces[0])
				s.SDF_GenerateRandom(nonces[1])
				thisNonces = [][]byte{
					append([]byte{}, nonces[0]...),
					append([]byte{}, nonces[1]...),
				}
				sk = aead.Seal(sk[:0], nonces[0], sk, []byte(url))
				pk = aead.Seal(pk[:0], nonces[1], pk, []byte(url))
			}
			km := &KeyMaterial{
				Type:      KeyTypeSm2Sign.String(),
				Url:       url,
				Nonces:    thisNonces,
				Materials: [][]byte{sk, pk},
			}
			snapshot.Keys = append(snapshot.Keys, km)
		}

		if v.encKey != nil {
			sk := v.encKey.Bytes()
			pk := v.encKey.PublicKey.Bytes()
			url := fmt.Sprintf("sm2enc/%d", k)
			thisNonces := nonces
			if aead != nil {
				s.SDF_GenerateRandom(nonces[0])
				s.SDF_GenerateRandom(nonces[1])
				thisNonces = [][]byte{
					append([]byte{}, nonces[0]...),
					append([]byte{}, nonces[1]...),
				}
				sk = aead.Seal(sk[:0], nonces[0], sk, []byte(url))
				pk = aead.Seal(pk[:0], nonces[1], pk, []byte(url))
			}
			km := &KeyMaterial{
				Type:      KeyTypeSm2Enc.String(),
				Url:       url,
				Nonces:    thisNonces,
				Materials: [][]byte{sk, pk},
			}
			snapshot.Keys = append(snapshot.Keys, km)
		}
	}
	for k, v := range s.SymKey {
		key := make([]byte, len(v.key), len(v.key)+16)
		url := fmt.Sprintf("kek/%d", k)
		thisNonces := nonces
		copy(key, v.key)
		if aead != nil {
			s.SDF_GenerateRandom(nonces[0])
			thisNonces = [][]byte{
				append([]byte{}, nonces[0]...),
			}
			key = aead.Seal(key[:0], nonces[0], key, []byte(url))
		}

		km := &KeyMaterial{
			Type:      v.keyType.String(),
			Url:       url,
			Nonces:    thisNonces,
			Materials: [][]byte{key},
		}
		snapshot.Keys = append(snapshot.Keys, km)
	}

	return snapshot, nil
}

func (s *SdfNoLock) Restore(ss *Snapshot, password []byte) error {
	var sdf = &SdfNoLock{
		RsaKey: make(map[uint32]*RsaKey),
		Sm2Key: make(map[uint32]*Sm2Key),
		SymKey: make(map[uint32]*SymKey),
	}
	var aead cipher.AEAD
	var err error
	if ss.Enc {
		var encryptionKey []byte
		if encryptionKey, err = pbkd.PbkdfWithHmacSm3([]byte(password), ss.EncParam.Salt, int(ss.EncParam.Count), sm4.BlockSize); err != nil {
			return err
		}

		if aead, err = sm4.NewGCM(encryptionKey); err != nil {
			return err
		}
	}

	for _, k := range ss.Keys {
		if k == nil {
			return fmt.Errorf("nil key")
		}
		switch GetKeyType(k.Type) {
		case KeyTypeBlock:
			if len(k.Materials) != 1 || len(k.Nonces) != len(k.Materials) {
				return fmt.Errorf("key material error")
			}
			key := k.Materials[0]
			if aead != nil {
				key, err = aead.Open(nil, k.Nonces[0], key, []byte(k.Url))
				if err != nil {
					return err
				}
			}
			urlSplits := strings.Split(k.Url, "/")
			if len(urlSplits) != 2 {
				return fmt.Errorf("key url error")
			}
			if strings.TrimSpace(urlSplits[0]) != "kek" {
				return fmt.Errorf("key url error")
			}
			index, err := strconv.Atoi(urlSplits[1])
			if err != nil {
				return fmt.Errorf("key url error")
			} else {
				sdf.SymKey[uint32(index)] = &SymKey{
					keyType: KeyTypeBlock,
					key:     key,
				}
			}
		case KeyTypeSm2Sign:
			if len(k.Materials) != 2 || len(k.Nonces) != len(k.Materials) {
				return fmt.Errorf("key material error")
			}
			sk := k.Materials[0]
			pk := k.Materials[1]
			if aead != nil {
				sk, err = aead.Open(nil, k.Nonces[0], sk, []byte(k.Url))
				if err != nil {
					return err
				}
				pk, err = aead.Open(nil, k.Nonces[1], pk, []byte(k.Url))
				if err != nil {
					return err
				}
			}
			urlSplits := strings.Split(k.Url, "/")
			if len(urlSplits) != 2 {
				return fmt.Errorf("key url error")
			}
			if strings.TrimSpace(urlSplits[0]) != "sm2sign" {
				return fmt.Errorf("key url error")
			}
			index, err := strconv.Atoi(urlSplits[1])
			if err != nil {
				return fmt.Errorf("key url error")
			} else {
				sm2PrivateKey := &sm2.PrivateKey{
					D: new(big.Int).SetBytes(sk),
				}
				sm2PrivateKey.PublicKey.Curve = sm2.Curve()
				sm2PrivateKey.PublicKey.X = new(big.Int).SetBytes(pk[:sm2.ByteSize()])
				sm2PrivateKey.PublicKey.Y = new(big.Int).SetBytes(pk[sm2.ByteSize():])

				if _, ok := sdf.Sm2Key[uint32(index)]; !ok {
					sdf.Sm2Key[uint32(index)] = &Sm2Key{}
				}
				sdf.Sm2Key[uint32(index)].signKey = sm2PrivateKey
			}

		case KeyTypeSm2Enc:
			if len(k.Materials) != 2 || len(k.Nonces) != len(k.Materials) {
				return fmt.Errorf("key material error")
			}
			sk := k.Materials[0]
			pk := k.Materials[1]
			if aead != nil {
				sk, err = aead.Open(nil, k.Nonces[0], sk, []byte(k.Url))
				if err != nil {
					return err
				}
				pk, err = aead.Open(nil, k.Nonces[1], pk, []byte(k.Url))
				if err != nil {
					return err
				}
			}
			urlSplits := strings.Split(k.Url, "/")
			if len(urlSplits) != 2 {
				return fmt.Errorf("key url error")
			}
			if strings.TrimSpace(urlSplits[0]) != "sm2enc" {
				return fmt.Errorf("key url error")
			}
			index, err := strconv.Atoi(urlSplits[1])
			if err != nil {
				return fmt.Errorf("key url error")
			} else {
				sm2PrivateKey := &sm2.PrivateKey{
					D: new(big.Int).SetBytes(sk),
				}
				sm2PrivateKey.PublicKey.Curve = sm2.Curve()
				sm2PrivateKey.PublicKey.X = new(big.Int).SetBytes(pk[:sm2.ByteSize()])
				sm2PrivateKey.PublicKey.Y = new(big.Int).SetBytes(pk[sm2.ByteSize():])

				if _, ok := sdf.Sm2Key[uint32(index)]; !ok {
					sdf.Sm2Key[uint32(index)] = &Sm2Key{}
				}
				sdf.Sm2Key[uint32(index)].encKey = sm2PrivateKey
			}
		case KeyTypeSm1:
			fallthrough
		case KeyTypeSm4:
			fallthrough
		case KeyTypeSm9MasterSign:
			fallthrough
		case KeyTypeSm9MasterEnc:
			fallthrough
		case KeyTypeSm9UserSign:
			fallthrough
		case KeyTypeSm9UserEnc:
			fallthrough
		case KeyTypeRsa:
			fallthrough
		default:
			return fmt.Errorf("key type errror")
		}
	}

	// now copy sdf to s
	s.Sm2Key = sdf.Sm2Key
	s.SymKey = sdf.SymKey
	s.RsaKey = sdf.RsaKey

	return nil

}
