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

package webapi

import (
	"errors"
	"sync"
	"time"

	"github.com/decred/vspd/database"
	"github.com/decred/vspd/rpc"
	"github.com/dustin/go-humanize"
)

// cache is used to store values which are commonly used by the API, so
// repeated web requests don't repeatedly trigger DB or RPC calls.
type cache struct {
	// data is the cached data.
	data cacheData
	// mtx must be held to read/write cache data.
	mtx sync.RWMutex
}

type cacheData struct {
	UpdateTime          string
	PubKey              string
	DatabaseSize        string
	Voting              int64
	Voted               int64
	Revoked             int64
	VotingWalletsOnline int64
	TotalVotingWallets  int64
	BlockHeight         uint32
	NetworkProportion   float32
	RevokedProportion   float32
}

func (c *cache) getData() cacheData {
	c.mtx.RLock()
	defer c.mtx.RUnlock()

	return c.data
}

// newCache creates a new cache and initializes it with static values.
func newCache(signPubKey string) *cache {
	return &cache{
		data: cacheData{
			PubKey: signPubKey,
		},
	}
}

// update will use the provided database and RPC connections to update the
// dynamic values in the cache.
func (c *cache) update(db *database.VspDatabase, dcrd rpc.DcrdConnect,
	wallets rpc.WalletConnect) error {

	dbSize, err := db.Size()
	if err != nil {
		return err
	}

	// Get latest counts of voting, voted and revoked tickets.
	voting, voted, revoked, err := db.CountTickets()
	if err != nil {
		return err
	}

	// Get latest best block height.
	dcrdClient, _, err := dcrd.Client()
	if err != nil {
		return err
	}

	bestBlock, err := dcrdClient.GetBestBlockHeader()
	if err != nil {
		return err
	}

	if bestBlock.PoolSize == 0 {
		return errors.New("dcr node reports a network ticket pool size of zero")
	}

	clients, failedConnections := wallets.Clients()
	if len(clients) == 0 {
		log.Error("Could not connect to any wallets")
	} else if len(failedConnections) > 0 {
		log.Errorf("Failed to connect to %d wallet(s), proceeding with only %d",
			len(failedConnections), len(clients))
	}

	c.mtx.Lock()
	defer c.mtx.Unlock()

	c.data.UpdateTime = dateTime(time.Now().Unix())
	c.data.DatabaseSize = humanize.Bytes(dbSize)
	c.data.Voting = voting
	c.data.Voted = voted
	c.data.TotalVotingWallets = int64(len(clients) + len(failedConnections))
	c.data.VotingWalletsOnline = int64(len(clients))
	c.data.Revoked = revoked
	c.data.BlockHeight = bestBlock.Height
	c.data.NetworkProportion = float32(voting) / float32(bestBlock.PoolSize)

	// Prevent dividing by zero when pool has no voted tickets.
	switch voted {
	case 0:
		if revoked == 0 {
			c.data.RevokedProportion = 0
		} else {
			c.data.RevokedProportion = 1
		}
	default:
		c.data.RevokedProportion = float32(revoked) / float32(voted)
	}

	return nil
}
