// Copyright 2019 CUE Authors
//
// 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 os

import (
	"os"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"

	"cuelang.org/go/cue"
	"cuelang.org/go/cue/ast"
	"cuelang.org/go/cue/errors"
	"cuelang.org/go/cue/parser"
	"cuelang.org/go/cue/token"
	"cuelang.org/go/internal/task"
	"cuelang.org/go/internal/value"
	"cuelang.org/go/pkg/internal"
)

func TestGetenv(t *testing.T) {

	for _, p := range [][2]string{
		{"CUEOSTESTMOOD", "yippie"},
		{"CUEOSTESTTRUE", "True"},
		{"CUEOSTESTFALSE", "0"},
		{"CUEOSTESTBI", "1"},
		{"CUEOSTESTNUM", "34K"},
		{"CUEOSTESTNUMD", "not a num"},
		{"CUEOSTESTMULTI", "10"},
	} {
		t.Setenv(p[0], p[1])
	}

	config := `{
		CUEOSTESTMOOD:  string
		CUEOSTESTTRUE:  bool
		CUEOSTESTFALSE: bool | string
		CUEOSTESTBI: 	*bool | int,
		CUEOSTESTNUM:   int
		CUEOSTESTNUMD:  *int | *bool | string
		CUEOSTESTMULTI: *<10 | string
		CUEOSTESTNULL:  int | null
	}`

	want := map[string]interface{}{
		"CUEOSTESTMOOD": ast.NewString("yippie"),
		"CUEOSTESTTRUE": ast.NewBool(true),
		"CUEOSTESTFALSE": &ast.BinaryExpr{
			Op: token.OR,
			X:  ast.NewBool(false),
			Y:  ast.NewString("0"),
		},
		"CUEOSTESTBI": &ast.BinaryExpr{
			Op: token.OR,
			X:  ast.NewLit(token.INT, "1"),
			Y:  ast.NewBool(true),
		},
		"CUEOSTESTNUM":  &ast.BasicLit{Kind: token.INT, Value: "34K"},
		"CUEOSTESTNUMD": ast.NewString("not a num"),
		"CUEOSTESTMULTI": &ast.BinaryExpr{
			Op: token.OR,
			X:  ast.NewLit(token.INT, "10"),
			Y:  ast.NewString("10"),
		},
		"CUEOSTESTNULL": nil,
	}

	for _, tc := range []struct {
		pkg    string
		runner task.Runner
	}{
		{"tool/os.Getenv", &getenvCmd{}},
		{"tool/os.Environ", &environCmd{}},
	} {
		v := parse(t, tc.pkg, config)
		got, err := tc.runner.Run(&task.Context{Obj: v})
		if err != nil {
			t.Fatal(err)
		}

		var opts = []cmp.Option{
			cmpopts.IgnoreFields(ast.BinaryExpr{}, "OpPos"),
			cmpopts.IgnoreFields(ast.BasicLit{}, "ValuePos"),
			cmpopts.IgnoreUnexported(ast.BasicLit{}, ast.BinaryExpr{}),
			// For ignoring addinonal entries from os.Environ:
			cmpopts.IgnoreMapEntries(func(s string, x interface{}) bool {
				_, ok := want[s]
				return !ok
			}),
		}

		if diff := cmp.Diff(got, want, opts...); diff != "" {
			t.Error(diff)
		}

		// Errors:
		for _, etc := range []struct{ config, err string }{{
			config: `{ CUEOSTESTNULL:  [...string] }`,
			err:    "expected unsupported type error",
		}, {
			config: `{ CUEOSTESTNUMD: int }`,
			err:    "expected invalid number error",
		}, {
			config: `{ CUEOSTESTNUMD: null }`,
			err:    "expected invalid type",
		}} {
			t.Run(etc.err, func(t *testing.T) {
				v = parse(t, tc.pkg, etc.config)
				if _, err = tc.runner.Run(&task.Context{Obj: v}); err == nil {
					t.Error(etc.err)
				}
			})
		}
	}
}

func parse(t *testing.T, kind, expr string) cue.Value {
	t.Helper()

	x, err := parser.ParseExpr("test", expr)
	if err != nil {
		errors.Print(os.Stderr, err, nil)
		t.Fatal(err)
	}
	ctx := internal.NewContext()
	v := ctx.BuildExpr(x)
	if err := v.Err(); err != nil {
		t.Fatal(err)
	}
	return value.UnifyBuiltin(v, kind)
}
