package stfe

import (
	"bytes"
	//"fmt"
	"reflect"
	"testing"
	//"testing/iotest"

	"net/http"

	"github.com/system-transparency/stfe/testdata"
	"github.com/system-transparency/stfe/types"
)

func TestParseAddEntryV1Request(t *testing.T) {
	lp := newLogParameters(t, nil)
	for _, table := range []struct {
		description string
		breq        *bytes.Buffer
		wantErr     bool
	}{
		{
			description: "invalid: nothing to unpack",
			breq:        bytes.NewBuffer(nil),
			wantErr:     true,
		},
		{
			description: "invalid: not a signed checksum entry",
			breq:        testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness, &testdata.Ed25519VkWitness),
			wantErr:     true,
		},
		{
			description: "invalid: untrusted submitter", // only testdata.Ed25519VkSubmitter is registered by default in newLogParameters()

			breq:    testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter2, testdata.Ed25519VkSubmitter2),
			wantErr: true,
		},
		{
			description: "invalid: signature does not cover message",

			breq:    testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter2, testdata.Ed25519VkSubmitter),
			wantErr: true,
		},
		{
			description: "valid",
			breq:        testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter, testdata.Ed25519VkSubmitter),
		}, // TODO: add test case that disables submitter policy (i.e., unregistered namespaces are accepted)
	} {
		url := EndpointAddEntry.Path("http://example.com", lp.Prefix)
		req, err := http.NewRequest("POST", url, table.breq)
		if err != nil {
			t.Fatalf("failed creating http request: %v", err)
		}
		req.Header.Set("Content-Type", "application/octet-stream")

		_, err = lp.parseAddEntryV1Request(req)
		if got, want := err != nil, table.wantErr; got != want {
			t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err)
		}
	}
}

func TestParseAddCosignatureV1Request(t *testing.T) {
	lp := newLogParameters(t, nil)
	for _, table := range []struct {
		description string
		breq        *bytes.Buffer
		wantErr     bool
	}{
		{
			description: "invalid: nothing to unpack",
			breq:        bytes.NewBuffer(nil),
			wantErr:     true,
		},
		{
			description: "invalid: not a cosigned sth",
			breq:        testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter, testdata.Ed25519VkSubmitter),
			wantErr:     true,
		},
		{
			description: "invalid: no cosignature",
			breq:        testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness, nil),
			wantErr:     true,
		},
		{
			description: "invalid: untrusted witness", // only testdata.Ed25519VkWitness is registered by default in newLogParameters()
			breq:        testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness2, &testdata.Ed25519VkWitness2),
			wantErr:     true,
		},
		{
			description: "invalid: signature does not cover message",
			breq:        testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness2, &testdata.Ed25519VkWitness),
			wantErr:     true,
		},
		{
			description: "valid",
			breq:        testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness, &testdata.Ed25519VkWitness),
		}, // TODO: add test case that disables witness policy (i.e., unregistered namespaces are accepted)
	} {
		url := EndpointAddCosignature.Path("http://example.com", lp.Prefix)
		req, err := http.NewRequest("POST", url, table.breq)
		if err != nil {
			t.Fatalf("failed creating http request: %v", err)
		}
		req.Header.Set("Content-Type", "application/octet-stream")

		_, err = lp.parseAddCosignatureV1Request(req)
		if got, want := err != nil, table.wantErr; got != want {
			t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err)
		}
	}
}

func TestNewGetConsistencyProofRequest(t *testing.T) {
	lp := newLogParameters(t, nil)
	for _, table := range []struct {
		description string
		req         *types.GetConsistencyProofV1
		wantErr     bool
	}{
		{
			description: "invalid: nothing to unpack",
			req:         nil,
			wantErr:     true,
		},
		{
			description: "invalid: first must be larger than zero",
			req:         &types.GetConsistencyProofV1{First: 0, Second: 0},
			wantErr:     true,
		},
		{
			description: "invalid: second must be larger than first",
			req:         &types.GetConsistencyProofV1{First: 2, Second: 1},
			wantErr:     true,
		},
		{
			description: "valid",
			req:         &types.GetConsistencyProofV1{First: 1, Second: 2},
		},
	} {
		var buf *bytes.Buffer
		if table.req == nil {
			buf = bytes.NewBuffer(nil)
		} else {
			buf = bytes.NewBuffer(marshal(t, *table.req))
		}

		url := EndpointGetConsistencyProof.Path("http://example.com", lp.Prefix)
		req, err := http.NewRequest("POST", url, buf)
		if err != nil {
			t.Fatalf("failed creating http request: %v", err)
		}
		req.Header.Set("Content-Type", "application/octet-stream")

		_, err = lp.parseGetConsistencyProofV1Request(req)
		if got, want := err != nil, table.wantErr; got != want {
			t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err)
		}
	}
}

func TestNewGetProofByHashRequest(t *testing.T) {
	lp := newLogParameters(t, nil)
	for _, table := range []struct {
		description string
		req         *types.GetProofByHashV1
		wantErr     bool
	}{
		{
			description: "invalid: nothing to unpack",
			req:         nil,
			wantErr:     true,
		},
		{
			description: "invalid: no entry in an empty tree",
			req:         &types.GetProofByHashV1{TreeSize: 0, Hash: testdata.LeafHash},
			wantErr:     true,
		},
		{
			description: "valid",
			req:         &types.GetProofByHashV1{TreeSize: 1, Hash: testdata.LeafHash},
		},
	} {
		var buf *bytes.Buffer
		if table.req == nil {
			buf = bytes.NewBuffer(nil)
		} else {
			buf = bytes.NewBuffer(marshal(t, *table.req))
		}

		url := EndpointGetProofByHash.Path("http://example.com", lp.Prefix)
		req, err := http.NewRequest("POST", url, buf)
		if err != nil {
			t.Fatalf("failed creating http request: %v", err)
		}
		req.Header.Set("Content-Type", "application/octet-stream")

		_, err = lp.parseGetProofByHashV1Request(req)
		if got, want := err != nil, table.wantErr; got != want {
			t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err)
		}
	}
}

func TestParseGetEntriesV1Request(t *testing.T) {
	lp := newLogParameters(t, nil)
	for _, table := range []struct {
		description string
		req         *types.GetEntriesV1
		wantErr     bool
		wantReq     *types.GetEntriesV1
	}{
		{
			description: "invalid: nothing to unpack",
			req:         nil,
			wantErr:     true,
		},
		{
			description: "invalid: start must be larger than end",
			req:         &types.GetEntriesV1{Start: 1, End: 0},
			wantErr:     true,
		},
		{
			description: "valid: want truncated range",
			req:         &types.GetEntriesV1{Start: 0, End: uint64(testdata.MaxRange)},
			wantReq:     &types.GetEntriesV1{Start: 0, End: uint64(testdata.MaxRange) - 1},
		},
		{
			description: "valid",
			req:         &types.GetEntriesV1{Start: 0, End: 0},
			wantReq:     &types.GetEntriesV1{Start: 0, End: 0},
		},
	} {
		var buf *bytes.Buffer
		if table.req == nil {
			buf = bytes.NewBuffer(nil)
		} else {
			buf = bytes.NewBuffer(marshal(t, *table.req))
		}

		url := EndpointGetEntries.Path("http://example.com", lp.Prefix)
		req, err := http.NewRequest("POST", url, buf)
		if err != nil {
			t.Fatalf("failed creating http request: %v", err)
		}
		req.Header.Set("Content-Type", "application/octet-stream")

		output, err := lp.parseGetEntriesV1Request(req)
		if got, want := err != nil, table.wantErr; got != want {
			t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err)
		}
		if err != nil {
			continue
		}
		if got, want := output, table.wantReq; !reflect.DeepEqual(got, want) {
			t.Errorf("got request\n%v\n\tbut wanted\n%v\n\t in test %q", got, want, table.description)
		}
	}
}

func TestUnpackOctetPost(t *testing.T) {
	for _, table := range []struct {
		description string
		req         *http.Request
		out         interface{}
		wantErr     bool
	}{
		//{
		//	description: "invalid: cannot read request body",
		//	req: func() *http.Request {
		//		req, err := http.NewRequest(http.MethodPost, "", iotest.ErrReader(fmt.Errorf("bad reader")))
		//		if err != nil {
		//			t.Fatalf("must make new http request: %v", err)
		//		}
		//		return req
		//	}(),
		//	out:     &types.StItem{},
		//	wantErr: true,
		//}, // testcase requires Go 1.16
		{
			description: "invalid: cannot unmarshal",
			req: func() *http.Request {
				req, err := http.NewRequest(http.MethodPost, "", bytes.NewBuffer(nil))
				if err != nil {
					t.Fatalf("must make new http request: %v", err)
				}
				return req
			}(),
			out:     &types.StItem{},
			wantErr: true,
		},
		{
			description: "valid",
			req: func() *http.Request {
				req, err := http.NewRequest(http.MethodPost, "", bytes.NewBuffer([]byte{0}))
				if err != nil {
					t.Fatalf("must make new http request: %v", err)
				}
				return req
			}(),
			out: &struct{ SomeUint8 uint8 }{},
		},
	} {
		err := unpackOctetPost(table.req, table.out)
		if got, want := err != nil, table.wantErr; got != want {
			t.Errorf("got error %v but wanted %v in test %q", got, want, table.description)
		}
	}
}

func marshal(t *testing.T, out interface{}) []byte {
	b, err := types.Marshal(out)
	if err != nil {
		t.Fatalf("must marshal: %v", err)
	}
	return b
}