// Copyright 2016 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 fixchain

import (
	"context"
	"net/http"
	"sync"
	"testing"

	"github.com/google/certificate-transparency-go/client"
	"github.com/google/certificate-transparency-go/jsonclient"
)

// NewLogger() test
func TestNewLogger(t *testing.T) {
	ctx := context.Background()
	// Test single chain posts.
	for i, test := range postTests {
		errors := make(chan *FixError)
		var wg sync.WaitGroup
		wg.Add(1)
		go func() {
			defer wg.Done()
			testErrors(t, i, test.expectedErrs, errors)
		}()

		c := &http.Client{Transport: &postTestRoundTripper{t: t, test: &test, testIndex: i}}
		logClient, err := client.New(test.url, c, jsonclient.Options{})
		if err != nil {
			t.Fatalf("failed to create LogClient: %v", err)
		}
		l := NewLogger(ctx, 1, errors, logClient, newNilLimiter(), false)

		l.QueueChain(extractTestChain(t, i, test.chain))
		l.Wait()

		close(l.errors)
		wg.Wait()

		// Check logger caching.
		if test.chain != nil {
			if test.ferr.Type == None && !l.postCertCache.get(hash(GetTestCertificateFromPEM(t, test.chain[0]))) {
				t.Errorf("#%d: leaf certificate not cached", i)
			}
			if !l.postChainCache.get(hashChain(extractTestChain(t, i, test.chain))) {
				t.Errorf("#%d: chain not cached", i)
			}
		}
	}
}

// NewLogger() test
func TestNewLoggerCaching(t *testing.T) {
	// Test logging multiple chains by looking at caching.
	ctx := context.Background()
	newLoggerTest := struct {
		url          string
		chains       [][]string
		expectedErrs []errorType
	}{
		"https://ct.googleapis.com/pilot",
		[][]string{
			{googleLeaf, thawteIntermediate, verisignRoot},
			{googleLeaf, thawteIntermediate, verisignRoot},
			{googleLeaf, thawteIntermediate},
			{testLeaf, testIntermediate2, testIntermediate1, testRoot},
		},
		[]errorType{},
	}

	errors := make(chan *FixError)
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		testErrors(t, 0, newLoggerTest.expectedErrs, errors)
	}()

	c := &http.Client{Transport: &newLoggerTestRoundTripper{}}
	logClient, err := client.New(newLoggerTest.url, c, jsonclient.Options{})
	if err != nil {
		t.Fatalf("failed to create LogClient: %v", err)
	}
	l := NewLogger(ctx, 5, errors, logClient, newNilLimiter(), false)

	for _, chain := range newLoggerTest.chains {
		l.QueueChain(extractTestChain(t, 0, chain))
	}
	l.Wait()
	close(l.errors)
	wg.Wait()

	// Check logger caching.
	seen := make(map[[hashSize]byte]bool)
	for i, chain := range newLoggerTest.chains {
		leafHash := hash(GetTestCertificateFromPEM(t, chain[0]))
		if !l.postCertCache.get(leafHash) {
			t.Errorf("Chain %d: leaf certificate not cached", i)
		}
		if !seen[leafHash] && !l.postChainCache.get(hashChain(extractTestChain(t, 0, chain))) {
			t.Errorf("Chain %d: chain not cached", i)
		}
		seen[leafHash] = true
	}
}

// Logger.postServer() test
func TestPostServer(t *testing.T) {
	ctx := context.Background()
	for i, test := range postTests {
		errors := make(chan *FixError)
		c := &http.Client{Transport: &postTestRoundTripper{t: t, test: &test, testIndex: i}}
		logClient, err := client.New(test.url, c, jsonclient.Options{})
		if err != nil {
			t.Fatalf("failed to create LogClient: %v", err)
		}
		l := &Logger{
			ctx:            ctx,
			client:         logClient,
			toPost:         make(chan *toPost),
			errors:         errors,
			limiter:        newNilLimiter(),
			postCertCache:  newLockedMap(),
			postChainCache: newLockedMap(),
		}
		var wg sync.WaitGroup
		wg.Add(1)
		go func() {
			defer wg.Done()
			testErrors(t, i, test.expectedErrs, errors)
		}()

		go l.postServer()
		l.QueueChain(extractTestChain(t, i, test.chain))
		l.Wait()

		close(l.errors)
		wg.Wait()
	}
}

// Logger.IsPosted() test
func TestIsPosted(t *testing.T) {
	isPostedTests := []struct {
		cert     string
		expected bool
	}{
		{
			googleLeaf,
			true,
		},
		{
			megaLeaf,
			true,
		},
		{
			testLeaf,
			false,
		},
		{
			testC,
			false,
		},
	}

	l := &Logger{postCertCache: newLockedMap()}
	l.postCertCache.set(hash(GetTestCertificateFromPEM(t, googleLeaf)), true)
	l.postCertCache.set(hash(GetTestCertificateFromPEM(t, megaLeaf)), true)
	l.postCertCache.set(hash(GetTestCertificateFromPEM(t, testLeaf)), false)

	for i, test := range isPostedTests {
		if l.IsPosted(GetTestCertificateFromPEM(t, test.cert)) != test.expected {
			t.Errorf("#%d: received %t, expected %t", i, !test.expected, test.expected)
		}
	}
}

// Logger.QueueChain() tests
type loggerQueueTest struct {
	chain         []string
	expectedChain []string
}

var loggerQueueTests = []loggerQueueTest{
	{
		chain:         []string{googleLeaf, thawteIntermediate, verisignRoot},
		expectedChain: []string{"Google", "Thawte", "VeriSign"},
	},
	{ // Add the same chain a second time to test chain caching.
		// Note that if chain caching isn't working correctly, the test will hang.
		chain: []string{googleLeaf, thawteIntermediate, verisignRoot},
	},
}

func testLoggerQueueChain(t *testing.T, i int, qt *loggerQueueTest, l *Logger) {
	defer l.wg.Done()
	if qt.expectedChain != nil {
		post := <-l.toPost
		matchTestChain(t, i, qt.expectedChain, post.chain)
		l.wg.Done() // Required as logger wg is incremented internally every time a toPost is added to the queue.
	}
}

func TestLoggerQueueChain(t *testing.T) {
	ch := make(chan *toPost)
	defer close(ch)
	l := &Logger{toPost: ch, postCertCache: newLockedMap(), postChainCache: newLockedMap()}

	for i, qt := range loggerQueueTests {
		l.wg.Add(1)
		go testLoggerQueueChain(t, i, &qt, l)
		chain := extractTestChain(t, i, qt.chain)
		l.QueueChain(chain)
		l.wg.Wait()
	}
}

// Logger.RootCerts() test
func TestRootCerts(t *testing.T) {
	ctx := context.Background()
	rootCertsTests := []struct {
		url           string
		expectedRoots []string
	}{
		{
			"https://ct.googleapis.com/pilot",
			[]string{verisignRoot, comodoRoot}, // These are not the actual roots for the pilot CT log, this is just for testing purposes.
		},
	}

	for i, test := range rootCertsTests {
		c := &http.Client{Transport: &rootCertsTestRoundTripper{}}
		logClient, err := client.New(test.url, c, jsonclient.Options{})
		if err != nil {
			t.Fatalf("failed to create LogClient: %v", err)
		}
		l := &Logger{ctx: ctx, client: logClient}
		roots := l.RootCerts()
		matchTestRoots(t, i, test.expectedRoots, roots)
	}
}
