package account

import (
	"sync"

	"github.com/aergoio/aergo-actor/actor"
	"github.com/aergoio/aergo-lib/log"
	"github.com/aergoio/aergo/v2/account/key"
	cfg "github.com/aergoio/aergo/v2/config"
	"github.com/aergoio/aergo/v2/contract/name"
	"github.com/aergoio/aergo/v2/pkg/component"
	"github.com/aergoio/aergo/v2/state"
	"github.com/aergoio/aergo/v2/state/statedb"
	"github.com/aergoio/aergo/v2/types"
	"github.com/aergoio/aergo/v2/types/message"
)

type AccountService struct {
	*component.BaseComponent
	cfg         *cfg.Config
	sdb         *state.ChainStateDB
	ks          *key.Store
	accountLock sync.RWMutex
	accounts    []*types.Account
	testConfig  bool
}

// NewAccountService create account service
func NewAccountService(cfg *cfg.Config, sdb *state.ChainStateDB) *AccountService {
	actor := &AccountService{
		cfg: cfg,
		sdb: sdb,
	}
	actor.BaseComponent = component.NewBaseComponent(message.AccountsSvc, actor, log.NewLogger("account"))

	return actor
}

func (as *AccountService) BeforeStart() {
	as.ks = key.NewStore(as.cfg.DataDir, as.cfg.Account.UnlockTimeout)

	as.accounts = []*types.Account{}
	addresses, err := as.ks.GetAddresses()
	if err != nil {
		as.Logger.Error().Err(err).Msg("could not open addresses")
	}
	for _, v := range addresses {
		as.accounts = append(as.accounts, &types.Account{Address: v})
	}
}

func (as *AccountService) AfterStart() {}

func (as *AccountService) BeforeStop() {
	as.ks.CloseStore()
	as.accounts = nil
}

func (as *AccountService) Statistics() *map[string]interface{} {
	return &map[string]interface{}{
		"totalaccounts": len(as.accounts),
		"personal":      as.cfg.Personal,
		"config":        as.cfg.Account,
	}
}
func (as *AccountService) resolveName(namedAddress []byte) ([]byte, error) {
	scs, err := statedb.GetNameAccountState(as.sdb.GetStateDB())
	if err != nil {
		return nil, err
	}
	return name.GetAddress(scs, namedAddress), nil
}

func (as *AccountService) Receive(context actor.Context) {

	switch msg := context.Message().(type) {
	case *message.GetAccounts:
		accountList := as.getAccounts()
		context.Respond(&message.GetAccountsRsp{Accounts: &types.AccountList{Accounts: accountList}})
	case *message.CreateAccount:
		account, _ := as.createAccount(msg.Passphrase)
		context.Respond(&message.CreateAccountRsp{Account: account})
	case *message.LockAccount:
		actualAddress := msg.Account.Address
		var err error
		if len(actualAddress) == types.NameLength {
			actualAddress, err = as.resolveName(actualAddress)
			if err != nil {
				context.Respond(&message.AccountRsp{
					Account: &types.Account{Address: actualAddress},
					Err:     err,
				})
			}
		}
		account, err := as.lockAccount(actualAddress, msg.Passphrase)
		context.Respond(&message.AccountRsp{Account: account, Err: err})
	case *message.UnlockAccount:
		actualAddress := msg.Account.Address
		var err error
		if len(actualAddress) == types.NameLength {
			actualAddress, err = as.resolveName(actualAddress)
			if err != nil {
				context.Respond(&message.AccountRsp{
					Account: &types.Account{Address: actualAddress},
					Err:     err,
				})
			}
		}
		account, err := as.unlockAccount(actualAddress, msg.Passphrase)
		context.Respond(&message.AccountRsp{Account: account, Err: err})
	case *message.ImportAccount:
		var account *types.Account
		var err error
		if msg.Wif != nil {
			account, err = as.importAccount(msg.Wif, msg.OldPass, msg.NewPass)
		} else {
			account, err = as.importAccountFromKeystore(msg.Keystore, msg.OldPass, msg.NewPass)
		}
		context.Respond(&message.ImportAccountRsp{Account: account, Err: err})
	case *message.ExportAccount:
		var wif []byte
		var err error
		if msg.AsKeystore {
			wif, err = as.exportAccountKeystore(msg.Account.Address, msg.Pass)
		} else {
			wif, err = as.exportAccount(msg.Account.Address, msg.Pass)
		}
		context.Respond(&message.ExportAccountRsp{Wif: wif, Err: err})
	case *message.SignTx:
		var err error
		actualAddress := msg.Tx.GetBody().GetAccount()
		if len(actualAddress) == types.NameLength {
			actualAddress, err = as.resolveName(msg.Tx.GetBody().GetAccount())
			if err != nil {
				context.Respond(&message.SignTxRsp{Tx: nil, Err: err})
			}
			msg.Requester = actualAddress
		}
		err = as.signTx(context, msg)
		if err != nil {
			context.Respond(&message.SignTxRsp{Tx: nil, Err: err})
		}
	case *message.VerifyTx:
		err := as.verifyTx(msg.Tx)
		if err != nil {
			context.Respond(&message.VerifyTxRsp{Tx: nil, Err: err})
		} else {
			context.Respond(&message.VerifyTxRsp{Tx: msg.Tx, Err: nil})
		}
	}
}

func (as *AccountService) getAccounts() []*types.Account {
	as.accountLock.RLock()
	defer as.accountLock.RUnlock()
	return as.accounts
}

func (as *AccountService) addAccount(account *types.Account) {
	as.accountLock.Lock()
	defer as.accountLock.Unlock()
	as.accounts = append(as.accounts, account)
}

func (as *AccountService) createAccount(passphrase string) (*types.Account, error) {
	address, err := as.ks.CreateKey(passphrase)
	if err != nil {
		return nil, err
	}
	account := types.NewAccount(address)
	as.addAccount(account)
	return account, nil
}

func (as *AccountService) importAccount(wif []byte, old string, new string) (*types.Account, error) {
	address, err := as.ks.ImportKey(wif, old, new)
	if err != nil {
		return nil, err
	}
	account := &types.Account{Address: address}
	as.addAccount(account)
	return account, nil
}

func (as *AccountService) importAccountFromKeystore(keystore []byte, old string, new string) (*types.Account, error) {
	privateKey, err := key.LoadKeystore(keystore, old)
	if err != nil {
		return nil, err
	}
	address, err := as.ks.AddKey(privateKey, new)
	if err != nil {
		return nil, err
	}
	account := &types.Account{Address: address}
	as.addAccount(account)
	return account, nil
}

func (as *AccountService) exportAccount(address []byte, pass string) ([]byte, error) {
	wif, err := as.ks.ExportKey(address, pass)
	if err != nil {
		return nil, err
	}
	return wif, nil
}

func (as *AccountService) exportAccountKeystore(address []byte, pass string) ([]byte, error) {
	privateKey, err := as.ks.GetKey(address, pass)
	if err != nil {
		return nil, err
	}
	wif, err := key.GetKeystore(privateKey, pass)
	if err != nil {
		return nil, err
	}
	return wif, nil
}

func (as *AccountService) unlockAccount(address []byte, passphrase string) (*types.Account, error) {
	addr, err := as.ks.Unlock(address, passphrase)
	if err != nil {
		as.Warn().Err(err).Msg("could not find the key")
		return nil, err
	}
	return &types.Account{Address: addr}, nil
}

func (as *AccountService) lockAccount(address []byte, passphrase string) (*types.Account, error) {
	addr, err := as.ks.Lock(address, passphrase)
	if err != nil {
		as.Warn().Err(err).Msg("could not load the key")
		return nil, err
	}
	return &types.Account{Address: addr}, nil
}

func (as *AccountService) signTx(c actor.Context, msg *message.SignTx) error {
	//sign tx
	prop := actor.FromInstance(NewSigner(as.ks))
	signer := c.Spawn(prop)
	signer.Request(msg, c.Sender())
	return nil
}

func (as *AccountService) verifyTx(tx *types.Tx) error {
	return as.ks.VerifyTx(tx)
}
