// Copyright 2016 Google LLC
//
// 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 translate

import (
	"context"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"strings"
	"sync"
	"testing"

	"cloud.google.com/go/internal/testutil"
	"golang.org/x/text/language"
	"google.golang.org/api/option"
)

var (
	once    sync.Once
	authOpt option.ClientOption
)

func initTest(ctx context.Context, t *testing.T) *Client {
	if testing.Short() {
		t.Skip("integration tests skipped in short mode")
	}
	once.Do(func() { authOpt = authOption() })
	if authOpt == nil {
		t.Skip("Integration tests skipped. See CONTRIBUTING.md for details")
	}
	client, err := NewClient(ctx, authOpt)
	if err != nil {
		t.Fatalf("NewClient: %v", err)
	}
	return client
}

func authOption() option.ClientOption {
	ts := testutil.TokenSource(context.Background(), Scope)
	if ts != nil {
		log.Println("authenticating via OAuth2")
		return option.WithTokenSource(ts)
	}
	apiKey := os.Getenv("GCLOUD_TESTS_API_KEY")
	if apiKey != "" {
		log.Println("authenticating with API key")
		return option.WithAPIKey(apiKey)
	}
	return nil
}

type fakeTransport struct {
	req *http.Request
}

func (t *fakeTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	t.req = req
	return &http.Response{
		Status:     fmt.Sprintf("%d OK", http.StatusOK),
		StatusCode: http.StatusOK,
		Body:       ioutil.NopCloser(strings.NewReader("{}")),
	}, nil
}

func TestTranslateURL(t *testing.T) {
	// The translate API has all inputs in the URL.
	// Make sure we generate the right one.
	ctx := context.Background()
	ft := &fakeTransport{}
	c, err := NewClient(ctx, option.WithHTTPClient(&http.Client{Transport: ft}))
	if err != nil {
		t.Fatal(err)
	}
	for _, test := range []struct {
		target language.Tag
		inputs []string
		opts   *Options
		want   url.Values
	}{
		{language.Spanish, []string{"text"}, nil, url.Values{
			"q":      []string{"text"},
			"target": []string{"es"},
		}},
		{language.English, []string{"text"}, &Options{}, url.Values{
			"q":      []string{"text"},
			"target": []string{"en"},
		}},
		{language.Turkish, []string{"t1", "t2"}, nil, url.Values{
			"q":      []string{"t1", "t2"},
			"target": []string{"tr"},
		}},
		{language.English, []string{"text"}, &Options{Source: language.French},
			url.Values{
				"q":      []string{"text"},
				"source": []string{"fr"},
				"target": []string{"en"},
			},
		},
		{language.English, []string{"text"}, &Options{Source: language.French, Format: HTML}, url.Values{
			"q":      []string{"text"},
			"source": []string{"fr"},
			"format": []string{"html"},
			"target": []string{"en"},
		}},
	} {
		_, err = c.Translate(ctx, test.inputs, test.target, test.opts)
		if err != nil {
			t.Fatal(err)
		}
		got := ft.req.URL.Query()
		test.want.Add("alt", "json")
		if !testutil.Equal(got, test.want) {
			t.Errorf("Translate(%s, %v, %+v):\ngot  %s\nwant %s",
				test.target, test.inputs, test.opts, got, test.want)
		}
	}
}

func TestTranslateOneInput(t *testing.T) {
	ctx := context.Background()
	c := initTest(ctx, t)
	defer c.Close()

	translate := func(input string, target language.Tag, opts *Options) Translation {
		ts, err := c.Translate(ctx, []string{input}, target, opts)
		if err != nil {
			t.Fatal(err)
		}
		if len(ts) != 1 {
			t.Fatalf("wanted one Translation, got %d", len(ts))
		}
		return ts[0]
	}

	for _, test := range []struct {
		input  string
		source language.Tag
		output string
		target language.Tag
	}{
		// https://www.youtube.com/watch?v=x1sQkEfAdfY
		{"Le singe est sur la branche", language.French,
			"The monkey is on the branch", language.English},
		{"The cat is on the chair", language.English,
			"Le chat est sur la chaise", language.French},
	} {
		// Provide source and format.
		tr := translate(test.input, test.target, &Options{Source: test.source, Format: Text})
		if got, want := tr.Source, language.Und; got != want {
			t.Errorf("source: got %q, wanted %q", got, want)
			continue
		}
		if got, want := tr.Text, test.output; got != want {
			t.Errorf("text: got %q, want %q", got, want)
		}
		// Omit source; it should be detected.
		tr = translate(test.input, test.target, &Options{Format: Text})
		if got, want := tr.Source, test.source; got != want {
			t.Errorf("source: got %q, wanted %q", got, want)
			continue
		}
		if got, want := tr.Text, test.output; got != want {
			t.Errorf("text: got %q, want %q", got, want)
		}

		// Omit format. Defaults to HTML. Still works with plain text.
		tr = translate(test.input, test.target, nil)
		if got, want := tr.Source, test.source; got != want {
			t.Errorf("source: got %q, wanted %q", got, want)
			continue
		}
		if got, want := tr.Text, test.output; got != want {
			t.Errorf("text: got %q, want %q", got, want)
		}

		// Add HTML tags to input. They should be in output.
		htmlify := func(s string) string {
			return "<b><i>" + s + "</i></b>"
		}
		tr = translate(htmlify(test.input), test.target, nil)
		if got, want := tr.Text, htmlify(test.output); got != want {
			t.Errorf("html: got %q, want %q", got, want)
		}
		// Using the HTML format behaves the same.
		tr = translate(htmlify(test.input), test.target, &Options{Format: HTML})
		if got, want := tr.Text, htmlify(test.output); got != want {
			t.Errorf("html: got %q, want %q", got, want)
		}
	}
}

// This tests the beta "nmt" model.
func TestTranslateModel(t *testing.T) {
	ctx := context.Background()
	c := initTest(ctx, t)
	defer c.Close()

	trs, err := c.Translate(ctx, []string{"Hello"}, language.French, &Options{Model: "nmt"})
	if err != nil {
		t.Fatal(err)
	}
	if len(trs) != 1 {
		t.Fatalf("wanted one Translation, got %d", len(trs))
	}
	tr := trs[0]
	if got, want := tr.Text, "Bonjour"; got != want {
		t.Errorf("text: got %q, want %q", got, want)
	}
	if got, want := tr.Model, "nmt"; got != want {
		t.Errorf("model: got %q, want %q", got, want)
	}
}

func TestTranslateMultipleInputs(t *testing.T) {
	ctx := context.Background()
	c := initTest(ctx, t)
	defer c.Close()

	inputs := []string{
		"When you're a Jet, you're a Jet all the way",
		"From your first cigarette to your last dying day",
		"When you're a Jet if the spit hits the fan",
		"You got brothers around, you're a family man",
	}
	ts, err := c.Translate(ctx, inputs, language.French, nil)
	if err != nil {
		t.Fatal(err)
	}
	if got, want := len(ts), len(inputs); got != want {
		t.Fatalf("got %d Translations, wanted %d", got, want)
	}
}

func TestTranslateErrors(t *testing.T) {
	ctx := context.Background()
	c := initTest(ctx, t)
	defer c.Close()

	for _, test := range []struct {
		ctx    context.Context
		target language.Tag
		inputs []string
		opts   *Options
	}{
		{ctx, language.English, nil, nil},
		{ctx, language.Und, []string{"input"}, nil},
		{ctx, language.English, []string{}, nil},
		{ctx, language.English, []string{"input"}, &Options{Format: "random"}},
	} {
		_, err := c.Translate(test.ctx, test.inputs, test.target, test.opts)
		if err == nil {
			t.Errorf("%+v: got nil, want error", test)
		}
	}
}

func TestDetectLanguage(t *testing.T) {
	ctx := context.Background()
	c := initTest(ctx, t)
	defer c.Close()
	ds, err := c.DetectLanguage(ctx, []string{
		"Today is Monday",
		"Aujourd'hui est lundi",
	})
	if err != nil {
		t.Fatal(err)
	}
	if len(ds) != 2 {
		t.Fatalf("got %d detection lists, want 2", len(ds))
	}
	checkDetections(t, ds[0], language.English)
	checkDetections(t, ds[1], language.French)
}

func checkDetections(t *testing.T, ds []Detection, want language.Tag) {
	for _, d := range ds {
		if d.Language == want {
			return
		}
	}
	t.Errorf("%v: missing %s", ds, want)
}

// A small subset of the supported languages.
var supportedLangs = []Language{
	{Name: "Danish", Tag: language.Danish},
	{Name: "English", Tag: language.English},
	{Name: "French", Tag: language.French},
	{Name: "German", Tag: language.German},
	{Name: "Greek", Tag: language.Greek},
	{Name: "Hindi", Tag: language.Hindi},
	{Name: "Hungarian", Tag: language.Hungarian},
	{Name: "Italian", Tag: language.Italian},
	{Name: "Russian", Tag: language.Russian},
	{Name: "Turkish", Tag: language.Turkish},
}

func TestSupportedLanguages(t *testing.T) {
	ctx := context.Background()
	c := initTest(ctx, t)
	defer c.Close()
	got, err := c.SupportedLanguages(ctx, language.English)
	if err != nil {
		t.Fatal(err)
	}
	want := map[language.Tag]Language{}
	for _, sl := range supportedLangs {
		want[sl.Tag] = sl
	}
	for _, g := range got {
		w, ok := want[g.Tag]
		if !ok {
			continue
		}
		if g != w {
			t.Errorf("got %+v, want %+v", g, w)
		}
		delete(want, g.Tag)
	}
	if len(want) > 0 {
		t.Errorf("missing: %+v", want)
	}
}
