// Copyright (c) 2020-2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package database

import (
	"errors"
	"fmt"

	bolt "go.etcd.io/bbolt"
)

// The keys used to store alternate signing addresses in the database.
var (
	altSignAddrK = []byte("altsig")
	reqK         = []byte("req")
	reqSigK      = []byte("reqsig")
	respK        = []byte("res")
	respSigK     = []byte("ressig")
)

// AltSignAddrData holds the information needed to prove that a client added an
// alternate signing address.
type AltSignAddrData struct {
	// AltSignAddr is the new alternate signing address. It is base 58 encoded.
	AltSignAddr string
	// Req is the original request to set an alternate signing address.
	Req string
	// ReqSig is the request's signature signed by the commitment address of the
	// corresponding ticket. It is base 64 encoded.
	ReqSig string
	// Resp is the original response from the server to the alternate signing
	// address.
	Resp string
	// RespSig is the response's signature signed by the server. It is base 64
	// encoded.
	RespSig string
}

// InsertAltSignAddr will insert the provided alternate signing address into the
// database. Returns an error if data for the ticket hash already exist.
//
// Passed data must have no empty fields.
func (vdb *VspDatabase) InsertAltSignAddr(ticketHash string, data *AltSignAddrData) error {
	if data == nil {
		return errors.New("alt sign addr data must not be nil for inserts")
	}

	if data.AltSignAddr == "" || len(data.Req) == 0 || data.ReqSig == "" ||
		len(data.Resp) == 0 || data.RespSig == "" {
		return errors.New("alt sign addr data has empty parameters")
	}

	return vdb.db.Update(func(tx *bolt.Tx) error {
		altSignAddrBkt := tx.Bucket(vspBktK).Bucket(altSignAddrBktK)

		// Create a bucket for the new alt sign addr. Returns an error if bucket
		// already exists.
		bkt, err := altSignAddrBkt.CreateBucket([]byte(ticketHash))
		if err != nil {
			return fmt.Errorf("could not create bucket for alt sign addr: %w", err)
		}

		if err := bkt.Put(altSignAddrK, []byte(data.AltSignAddr)); err != nil {
			return err
		}

		if err := bkt.Put(reqK, []byte(data.Req)); err != nil {
			return err
		}

		if err := bkt.Put(reqSigK, []byte(data.ReqSig)); err != nil {
			return err
		}

		if err := bkt.Put(respK, []byte(data.Resp)); err != nil {
			return err
		}
		return bkt.Put(respSigK, []byte(data.RespSig))
	})
}

// DeleteAltSignAddr deletes an alternate signing address from the database.
// Does not error if there is no record in the database to delete.
func (vdb *VspDatabase) DeleteAltSignAddr(ticketHash string) error {
	return vdb.db.Update(func(tx *bolt.Tx) error {
		altSignAddrBkt := tx.Bucket(vspBktK).Bucket(altSignAddrBktK)

		// Don't attempt delete if doesn't exist.
		bkt := altSignAddrBkt.Bucket([]byte(ticketHash))
		if bkt == nil {
			return nil
		}

		err := altSignAddrBkt.DeleteBucket([]byte(ticketHash))
		if err != nil {
			return fmt.Errorf("could not delete altsignaddr: %w", err)
		}

		return nil
	})
}

// AltSignAddrData retrieves a ticket's alternate signing data. Existence of an
// alternate signing address can be inferred by no error and nil data return.
func (vdb *VspDatabase) AltSignAddrData(ticketHash string) (*AltSignAddrData, error) {
	var h *AltSignAddrData
	return h, vdb.db.View(func(tx *bolt.Tx) error {
		bkt := tx.Bucket(vspBktK).Bucket(altSignAddrBktK).Bucket([]byte(ticketHash))
		if bkt == nil {
			return nil
		}
		h = &AltSignAddrData{
			AltSignAddr: string(bkt.Get(altSignAddrK)),
			Req:         string(bkt.Get(reqK)),
			ReqSig:      string(bkt.Get(reqSigK)),
			Resp:        string(bkt.Get(respK)),
			RespSig:     string(bkt.Get(respSigK)),
		}
		return nil
	})
}
