// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package evm

import (
	"errors"
	"fmt"

	"github.com/ava-labs/avalanchego/database"
	"github.com/ava-labs/avalanchego/ids"
	"github.com/ava-labs/avalanchego/utils/crypto"
	"github.com/ethereum/go-ethereum/common"
)

// Key in the database whose corresponding value is the list of
// addresses this user controls
var addressesKey = ids.Empty.Bytes()

var (
	errDBNil        = errors.New("db uninitialized")
	errKeyNil       = errors.New("key uninitialized")
	errEmptyAddress = errors.New("address is empty")
)

type user struct {
	// This user's database, acquired from the keystore
	db database.Database
}

// Get the addresses controlled by this user
func (u *user) getAddresses() ([]common.Address, error) {
	if u.db == nil {
		return nil, errDBNil
	}

	// If user has no addresses, return empty list
	hasAddresses, err := u.db.Has(addressesKey)
	if err != nil {
		return nil, err
	}
	if !hasAddresses {
		return nil, nil
	}

	// User has addresses. Get them.
	bytes, err := u.db.Get(addressesKey)
	if err != nil {
		return nil, err
	}
	addresses := []common.Address{}
	if err := Codec.Unmarshal(bytes, &addresses); err != nil {
		return nil, err
	}
	return addresses, nil
}

// controlsAddress returns true iff this user controls the given address
func (u *user) controlsAddress(address common.Address) (bool, error) {
	if u.db == nil {
		return false, errDBNil
		//} else if address.IsZero() {
		//	return false, errEmptyAddress
	}
	return u.db.Has(address.Bytes())
}

// putAddress persists that this user controls address controlled by [privKey]
func (u *user) putAddress(privKey *crypto.PrivateKeySECP256K1R) error {
	if privKey == nil {
		return errKeyNil
	}

	address := GetEthAddress(privKey) // address the privKey controls
	controlsAddress, err := u.controlsAddress(address)
	if err != nil {
		return err
	}
	if controlsAddress { // user already controls this address. Do nothing.
		return nil
	}

	if err := u.db.Put(address.Bytes(), privKey.Bytes()); err != nil { // Address --> private key
		return err
	}

	addresses := make([]common.Address, 0) // Add address to list of addresses user controls
	userHasAddresses, err := u.db.Has(addressesKey)
	if err != nil {
		return err
	}
	if userHasAddresses { // Get addresses this user already controls, if they exist
		if addresses, err = u.getAddresses(); err != nil {
			return err
		}
	}
	addresses = append(addresses, address)
	bytes, err := Codec.Marshal(addresses)
	if err != nil {
		return err
	}
	if err := u.db.Put(addressesKey, bytes); err != nil {
		return err
	}
	return nil
}

// Key returns the private key that controls the given address
func (u *user) getKey(address common.Address) (*crypto.PrivateKeySECP256K1R, error) {
	if u.db == nil {
		return nil, errDBNil
		//} else if address.IsZero() {
		//	return nil, errEmptyAddress
	}

	factory := crypto.FactorySECP256K1R{}
	bytes, err := u.db.Get(address.Bytes())
	if err != nil {
		return nil, err
	}
	sk, err := factory.ToPrivateKey(bytes)
	if err != nil {
		return nil, err
	}
	if sk, ok := sk.(*crypto.PrivateKeySECP256K1R); ok {
		return sk, nil
	}
	return nil, fmt.Errorf("expected private key to be type *crypto.PrivateKeySECP256K1R but is type %T", sk)
}

// Return all private keys controlled by this user
func (u *user) getKeys() ([]*crypto.PrivateKeySECP256K1R, error) {
	addrs, err := u.getAddresses()
	if err != nil {
		return nil, err
	}
	keys := make([]*crypto.PrivateKeySECP256K1R, len(addrs))
	for i, addr := range addrs {
		key, err := u.getKey(addr)
		if err != nil {
			return nil, err
		}
		keys[i] = key
	}
	return keys, nil
}