// Copyright 2011 Google Inc. All Rights Reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.

package datastore

import (
	"bytes"
	"encoding/gob"
	"encoding/json"
	"testing"

	"github.com/golang/protobuf/proto"

	"google.golang.org/appengine"
	"google.golang.org/appengine/internal"
)

func TestKeyEncoding(t *testing.T) {
	testCases := []struct {
		desc string
		key  *Key
		exp  string
	}{
		{
			desc: "A simple key with an int ID",
			key: &Key{
				kind:  "Person",
				intID: 1,
				appID: "glibrary",
			},
			exp: "aghnbGlicmFyeXIMCxIGUGVyc29uGAEM",
		},
		{
			desc: "A simple key with a string ID",
			key: &Key{
				kind:     "Graph",
				stringID: "graph:7-day-active",
				appID:    "glibrary",
			},
			exp: "aghnbGlicmFyeXIdCxIFR3JhcGgiEmdyYXBoOjctZGF5LWFjdGl2ZQw",
		},
		{
			desc: "A key with a parent",
			key: &Key{
				kind:  "WordIndex",
				intID: 1033,
				parent: &Key{
					kind:  "WordIndex",
					intID: 1020032,
					appID: "glibrary",
				},
				appID: "glibrary",
			},
			exp: "aghnbGlicmFyeXIhCxIJV29yZEluZGV4GIChPgwLEglXb3JkSW5kZXgYiQgM",
		},
	}
	for _, tc := range testCases {
		enc := tc.key.Encode()
		if enc != tc.exp {
			t.Errorf("%s: got %q, want %q", tc.desc, enc, tc.exp)
		}

		key, err := DecodeKey(tc.exp)
		if err != nil {
			t.Errorf("%s: failed decoding key: %v", tc.desc, err)
			continue
		}
		if !key.Equal(tc.key) {
			t.Errorf("%s: decoded key %v, want %v", tc.desc, key, tc.key)
		}
	}
}

func TestKeyGob(t *testing.T) {
	k := &Key{
		kind:  "Gopher",
		intID: 3,
		parent: &Key{
			kind:     "Mom",
			stringID: "narwhal",
			appID:    "gopher-con",
		},
		appID: "gopher-con",
	}

	buf := new(bytes.Buffer)
	if err := gob.NewEncoder(buf).Encode(k); err != nil {
		t.Fatalf("gob encode failed: %v", err)
	}

	k2 := new(Key)
	if err := gob.NewDecoder(buf).Decode(k2); err != nil {
		t.Fatalf("gob decode failed: %v", err)
	}
	if !k2.Equal(k) {
		t.Errorf("gob round trip of %v produced %v", k, k2)
	}
}

func TestNilKeyGob(t *testing.T) {
	type S struct {
		Key *Key
	}
	s1 := new(S)

	buf := new(bytes.Buffer)
	if err := gob.NewEncoder(buf).Encode(s1); err != nil {
		t.Fatalf("gob encode failed: %v", err)
	}

	s2 := new(S)
	if err := gob.NewDecoder(buf).Decode(s2); err != nil {
		t.Fatalf("gob decode failed: %v", err)
	}
	if s2.Key != nil {
		t.Errorf("gob round trip of nil key produced %v", s2.Key)
	}
}

func TestKeyJSON(t *testing.T) {
	k := &Key{
		kind:  "Gopher",
		intID: 2,
		parent: &Key{
			kind:     "Mom",
			stringID: "narwhal",
			appID:    "gopher-con",
		},
		appID: "gopher-con",
	}
	exp := `"` + k.Encode() + `"`

	buf, err := json.Marshal(k)
	if err != nil {
		t.Fatalf("json.Marshal failed: %v", err)
	}
	if s := string(buf); s != exp {
		t.Errorf("JSON encoding of key %v: got %q, want %q", k, s, exp)
	}

	k2 := new(Key)
	if err := json.Unmarshal(buf, k2); err != nil {
		t.Fatalf("json.Unmarshal failed: %v", err)
	}
	if !k2.Equal(k) {
		t.Errorf("JSON round trip of %v produced %v", k, k2)
	}
}

func TestNilKeyJSON(t *testing.T) {
	type S struct {
		Key *Key
	}
	s1 := new(S)

	buf, err := json.Marshal(s1)
	if err != nil {
		t.Fatalf("json.Marshal failed: %v", err)
	}

	s2 := new(S)
	if err := json.Unmarshal(buf, s2); err != nil {
		t.Fatalf("json.Unmarshal failed: %v", err)
	}
	if s2.Key != nil {
		t.Errorf("JSON round trip of nil key produced %v", s2.Key)
	}
}

type fakeKeyer struct {
	appengine.Context
}

func (fakeKeyer) FullyQualifiedAppID() string { return "s~some-app" }
func (fakeKeyer) Call(service, method string, in, out proto.Message, opts *internal.CallOptions) error {
	return nil
}

func TestIncompleteKeyWithParent(t *testing.T) {
	var c appengine.Context = fakeKeyer{}

	// fadduh is a complete key.
	fadduh := NewKey(c, "Person", "", 1, nil)
	if fadduh.Incomplete() {
		t.Fatalf("fadduh is incomplete")
	}

	// robert is an incomplete key with fadduh as a parent.
	robert := NewIncompleteKey(c, "Person", fadduh)
	if !robert.Incomplete() {
		t.Fatalf("robert is complete")
	}

	// Both should be valid keys.
	if !fadduh.valid() {
		t.Errorf("fadduh is invalid: %v", fadduh)
	}
	if !robert.valid() {
		t.Errorf("robert is invalid: %v", robert)
	}
}

func TestNamespace(t *testing.T) {
	key := &Key{
		kind:      "Person",
		intID:     1,
		appID:     "s~some-app",
		namespace: "mynamespace",
	}
	if g, w := key.Namespace(), "mynamespace"; g != w {
		t.Errorf("key.Namespace() = %q, want %q", g, w)
	}
}
