// Copyright 2017 Google LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package testonly

import (
	"bytes"
	"context"
	"fmt"

	"github.com/google/trillian/storage/tree"
	"github.com/transparency-dev/merkle/compact"
	"github.com/transparency-dev/merkle/rfc6962"
	"k8s.io/klog/v2"
)

// This is a fake implementation of a NodeReader intended for use in testing Merkle path code.
// Building node sets for tests by hand is onerous and error prone, especially when trying
// to test code reading from multiple tree revisions. It cannot live in the main testonly
// package as this creates import cycles.

// FakeNodeReader is an implementation of storage.NodeReader that's preloaded with a set of
// NodeID -> Node mappings and will return only those. Requesting any other nodes results in
// an error. For use in tests only, does not implement any other storage APIs.
type FakeNodeReader struct {
	nodeMap map[compact.NodeID]tree.Node
}

// NewFakeNodeReader creates and returns a FakeNodeReader with the supplied nodes
// assuming that all the nodes are at a specified tree revision. All the node IDs
// must be distinct.
func NewFakeNodeReader(nodes []tree.Node) *FakeNodeReader {
	nodeMap := make(map[compact.NodeID]tree.Node)

	for _, node := range nodes {
		id := node.ID
		if _, ok := nodeMap[id]; ok {
			// Duplicate mapping - the test data is invalid so don't continue.
			klog.Fatalf("NewFakeNodeReader duplicate mapping for: %+v in:\n%v", id, nodes)
		}
		nodeMap[id] = node
	}

	return &FakeNodeReader{nodeMap: nodeMap}
}

// GetMerkleNodes implements the corresponding NodeReader API.
func (f FakeNodeReader) GetMerkleNodes(ids []compact.NodeID) ([]tree.Node, error) {
	nodes := make([]tree.Node, 0, len(ids))
	for _, id := range ids {
		node, ok := f.nodeMap[id]
		if !ok {
			return nil, fmt.Errorf("GetMerkleNodes() unknown node ID: %v", id)
		}
		nodes = append(nodes, node)
	}
	return nodes, nil
}

func (f FakeNodeReader) hasID(id compact.NodeID) bool {
	_, ok := f.nodeMap[id]
	return ok
}

// MultiFakeNodeReader can provide nodes at multiple revisions. It delegates to a number of
// FakeNodeReaders, each set up to handle one revision.
type MultiFakeNodeReader struct {
	readers []FakeNodeReader
}

// LeafBatch describes a set of leaves to be loaded into a MultiFakeNodeReader via a compact
// merkle tree. As each batch is added to the tree a set of node updates are collected
// and recorded in a FakeNodeReader for that revision. The expected root should be the
// result of calling CurrentRoot() on the compact Merkle tree encoded by hex.EncodeToString().
type LeafBatch struct {
	TreeRevision int64
	Leaves       []string
	ExpectedRoot []byte
}

// NewMultiFakeNodeReader creates a MultiFakeNodeReader delegating to a number of FakeNodeReaders
func NewMultiFakeNodeReader(readers []FakeNodeReader) *MultiFakeNodeReader {
	return &MultiFakeNodeReader{readers: readers}
}

// NewMultiFakeNodeReaderFromLeaves uses a compact Merkle tree to set up the nodes at various
// revisions. It collates all node updates from a batch of leaf data into one FakeNodeReader.
// This has the advantage of not needing to manually create all the data structures but the
// disadvantage is that a bug in the compact tree could be reflected in test using this
// code. To help guard against this we check the tree root hash after each batch has been
// processed. The supplied batches should be in ascending order of tree revision.
func NewMultiFakeNodeReaderFromLeaves(batches []LeafBatch) *MultiFakeNodeReader {
	hasher := rfc6962.DefaultHasher
	fact := compact.RangeFactory{Hash: hasher.HashChildren}
	cr := fact.NewEmptyRange(0)

	readers := make([]FakeNodeReader, 0, len(batches))

	lastBatchRevision := int64(0)
	for _, batch := range batches {
		if batch.TreeRevision <= lastBatchRevision {
			klog.Fatalf("Batches out of order revision: %d, last: %d in:\n%v", batch.TreeRevision,
				lastBatchRevision, batches)
		}

		lastBatchRevision = batch.TreeRevision
		nodeMap := make(map[compact.NodeID][]byte)
		store := func(id compact.NodeID, hash []byte) { nodeMap[id] = hash }
		for _, leaf := range batch.Leaves {
			hash := hasher.HashLeaf([]byte(leaf))
			// Store the new leaf node, and all new perfect nodes.
			if err := cr.Append(hash, store); err != nil {
				panic(fmt.Errorf("Append: %v", err))
			}
		}
		// TODO(pavelkalinnikov): Use testing.T.Fatalf instead of panics.
		root, err := cr.GetRootHash(nil)
		if err != nil {
			panic(fmt.Errorf("GetRootHash: %v", err))
		}
		if cr.End() == 0 {
			root = hasher.EmptyRoot()
		}
		// Sanity check the tree root hash against the one we expect to see.
		if got, want := root, batch.ExpectedRoot; !bytes.Equal(got, want) {
			panic(fmt.Errorf("NewMultiFakeNodeReaderFromLeaves() got root: %x, want: %x (%v)", got, want, batch))
		}

		// Unroll the update map to []tree.Node to retain the most recent node update within
		// the batch for each ID. Use that to create a new FakeNodeReader.
		nodes := make([]tree.Node, 0, len(nodeMap))
		for id, hash := range nodeMap {
			nodes = append(nodes, tree.Node{ID: id, Hash: hash})
		}

		readers = append(readers, *NewFakeNodeReader(nodes))
	}

	return NewMultiFakeNodeReader(readers)
}

func (m MultiFakeNodeReader) readerForNodeID(id compact.NodeID) *FakeNodeReader {
	// Work backwards and use the first reader where the node is present.
	for i := len(m.readers) - 1; i >= 0; i-- {
		if m.readers[i].hasID(id) {
			return &m.readers[i]
		}
	}
	return nil
}

// GetMerkleNodes implements the corresponding NodeReader API.
func (m MultiFakeNodeReader) GetMerkleNodes(ctx context.Context, ids []compact.NodeID) ([]tree.Node, error) {
	// Find the correct reader for the supplied tree revision. This must be done for each node
	// as earlier revisions may still be relevant
	nodes := make([]tree.Node, 0, len(ids))
	for _, id := range ids {
		reader := m.readerForNodeID(id)

		if reader == nil {
			return nil,
				fmt.Errorf("want nodeID %v, but no reader has it\n%v", id, m)
		}

		node, err := reader.GetMerkleNodes([]compact.NodeID{id})
		if err != nil {
			return nil, err
		}
		nodes = append(nodes, node[0])
	}
	return nodes, nil
}
