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

package main

import (
	"bytes"
	"strings"
	"testing"

	"github.com/decred/base58"
	"github.com/decred/dcrd/blockchain/standalone/v2"
	"github.com/decred/dcrd/chaincfg/v3"
)

// checkPowLimitsAreConsistent ensures PowLimit and PowLimitBits are consistent
// with each other.
func checkPowLimitsAreConsistent(t *testing.T, params *chaincfg.Params) {
	powLimitBigInt := params.PowLimit
	powLimitCompact := params.PowLimitBits

	toBig := standalone.CompactToBig(powLimitCompact)
	toCompact := standalone.BigToCompact(powLimitBigInt)

	// Check params.PowLimitBits matches params.PowLimit converted
	// into the compact form
	if toCompact != powLimitCompact {
		t.Fatalf("PowLimit values mismatch:\n"+
			"params.PowLimit    :%064x\n"+
			"                   :%x\n"+
			"params.PowLimitBits:%064x\n"+
			"                   :%x\n"+
			"params.PowLimit is not consistent with the params.PowLimitBits",
			powLimitBigInt, toCompact, toBig, powLimitCompact)
	}
}

// checkGenesisBlockRespectsNetworkPowLimit ensures genesis.Header.Bits value
// is within the network PoW limit.
//
// Genesis header bits define starting difficulty of the network.
// Header bits of each block define target difficulty of the subsequent block.
//
// The first few solved blocks of the network will inherit the genesis block
// bits value before the difficulty readjustment takes place.
//
// Solved block shouldn't be rejected due to the PoW limit check.
//
// This test ensures these blocks will respect the network PoW limit.
func checkGenesisBlockRespectsNetworkPowLimit(t *testing.T, params *chaincfg.Params) {
	genesis := params.GenesisBlock
	bits := genesis.Header.Bits

	// Header bits as big.Int
	bitsAsBigInt := standalone.CompactToBig(bits)

	// network PoW limit
	powLimitBigInt := params.PowLimit

	if bitsAsBigInt.Cmp(powLimitBigInt) > 0 {
		t.Fatalf("Genesis block fails the consensus:\n"+
			"genesis.Header.Bits:%x\n"+
			"                   :%064x\n"+
			"params.PowLimit    :%064x\n"+
			"genesis.Header.Bits "+
			"should respect network PoW limit",
			bits, bitsAsBigInt, powLimitBigInt)
	}
}

// checkPrefix checks if targetString starts with the given prefix
func checkPrefix(t *testing.T, prefix string, targetString, networkName string) {
	if strings.Index(targetString, prefix) != 0 {
		t.Logf("Address prefix mismatch for <%s>: expected <%s> received <%s>",
			networkName, prefix, targetString)
		t.FailNow()
	}
}

// checkInterval creates two corner cases defining interval
// of all key values: [ xxxx000000000...0cccc , xxxx111111111...1cccc ],
// where xxxx - is the encoding magic, and cccc is a checksum.
// The interval is mapped to corresponding interval in base 58.
// Then prefixes are checked for mismatch.
func checkInterval(t *testing.T, desiredPrefix string, keySize int, networkName string, magic [2]byte) {
	// min and max possible keys
	// all zeroes
	minKey := bytes.Repeat([]byte{0x00}, keySize)
	// all ones
	maxKey := bytes.Repeat([]byte{0xff}, keySize)

	base58interval := [2]string{
		base58.CheckEncode(minKey, magic),
		base58.CheckEncode(maxKey, magic),
	}
	checkPrefix(t, desiredPrefix, base58interval[0], networkName)
	checkPrefix(t, desiredPrefix, base58interval[1], networkName)
}

// checkAddressPrefixesAreConsistent ensures address encoding magics and
// NetworkAddressPrefix are consistent with each other.
// This test will light red when a new network is started with incorrect values.
func checkAddressPrefixesAreConsistent(t *testing.T, privateKeyPrefix string, params *chaincfg.Params) {
	P := params.NetworkAddressPrefix

	// Desired prefixes
	Pk := P + "k"
	Ps := P + "s"
	Pe := P + "e"
	PS := P + "S"
	Pc := P + "c"
	pk := privateKeyPrefix

	checkInterval(t, Pk, 33, params.Name, params.PubKeyAddrID)
	checkInterval(t, Ps, 20, params.Name, params.PubKeyHashAddrID)
	checkInterval(t, Pe, 20, params.Name, params.PKHEdwardsAddrID)
	checkInterval(t, PS, 20, params.Name, params.PKHSchnorrAddrID)
	checkInterval(t, Pc, 20, params.Name, params.ScriptHashAddrID)
	checkInterval(t, pk, 33, params.Name, params.PrivateKeyID)
}

// TestDecredNetworkSettings checks Network-specific settings
func TestDecredNetworkSettings(t *testing.T) {
	mainNetParams := chaincfg.MainNetParams()
	testNet3Params := chaincfg.TestNet3Params()
	simNetParams := chaincfg.SimNetParams()
	regNetParams := chaincfg.RegNetParams()

	checkPowLimitsAreConsistent(t, mainNetParams)
	checkPowLimitsAreConsistent(t, testNet3Params)
	checkPowLimitsAreConsistent(t, simNetParams)
	checkPowLimitsAreConsistent(t, regNetParams)

	checkGenesisBlockRespectsNetworkPowLimit(t, mainNetParams)
	checkGenesisBlockRespectsNetworkPowLimit(t, testNet3Params)
	checkGenesisBlockRespectsNetworkPowLimit(t, simNetParams)
	checkGenesisBlockRespectsNetworkPowLimit(t, regNetParams)

	checkAddressPrefixesAreConsistent(t, "Pm", mainNetParams)
	checkAddressPrefixesAreConsistent(t, "Pt", testNet3Params)
	checkAddressPrefixesAreConsistent(t, "Ps", simNetParams)
	checkAddressPrefixesAreConsistent(t, "Pr", regNetParams)
}
