package typescript_test

import (
	"fmt"
	"log"
	"os/exec"
	"strings"
	"testing"

	"code.justin.tv/spade/code-generator/internal"
	"code.justin.tv/spade/code-generator/internal/typescript"
	"github.com/stretchr/testify/assert"
)

func TestGenerateExpectationsDefinitions(t *testing.T) {
	o := &strings.Builder{}
	schema, err := internal.GetYamlSchema("hardcode", strings.NewReader(schema))
	assert.NoError(t, err)

	typescript.GenerateExpectationsDefinitions(o, *schema)
	internal.P(o, `console.log("ok")`)

	// this only asserts that the code is valid typescript,
	// we run full integrations test of the generate output in generate_test.go
	assert.Equal(t, "ok", runTS(o.String(), true))
}

func TestGenerateValuesToNotBeNull(t *testing.T) {
	cases := []struct {
		input  string
		output string
	}{
		// null
		{input: "null", output: "expected to be defined"},
		{input: "undefined", output: "expected to be defined"},
		{input: `""`, output: "expected to not be empty string"},

		// not null
		{input: `"true"`, output: "undefined"},
		{input: `"hello"`, output: "undefined"},
		{input: `"false"`, output: "undefined"},
	}

	for _, tt := range cases {
		t.Run(fmt.Sprintf("%s_%s", tt.input, tt.output), func(t *testing.T) {
			o := &strings.Builder{}

			// define the function & call it
			typescript.GenerateValuesToNotBeNullDefinition(o)
			internal.P(o, "console.log(", typescript.GenerateValuesToNotBeNullCall(tt.input), "?.message)")

			// run the code and assert ouput
			assert.Equal(t, tt.output, runTS(o.String(), true))
		})
	}
}

func TestGenerateValuesToMatchRegexList_OK(t *testing.T) {
	cases := []struct {
		input   string
		regexes string
		output  string
	}{
		{input: `"def"`, regexes: "[/abc/, /def/, /ijk/]", output: "undefined"},
		{input: `"xyz"`, regexes: "[/abc/, /def/, /ijk/]", output: "expected to match one of /abc/,/def/,/ijk/ got xyz"},
	}

	for _, tt := range cases {
		t.Run(fmt.Sprintf("%s_%s_%s", tt.input, tt.regexes, tt.output), func(t *testing.T) {
			o := &strings.Builder{}

			// define the function & call it
			typescript.GenerateValuesToMatchRegexListDefinition(o)
			internal.P(o, "console.log(", typescript.GenerateValuesToMatchRegexListCall(tt.input, tt.regexes), "?.message)")

			// run the code and assert ouput
			assert.Equal(t, tt.output, runTS(o.String(), true))
		})
	}
}

func TestGenerateValuesToBeBetweenNumber_OK(t *testing.T) {
	cases := []struct {
		input  string
		min    string
		max    string
		output string
	}{
		{input: `1`, min: "1", max: "10", output: "undefined"},
		{input: `9`, min: "1", max: "10", output: "undefined"},
		{input: `10`, min: "1", max: "10", output: "undefined"},
		{input: `0`, min: "1", max: "10", output: "expected to be between 1 and 10 got 0"},
		{input: `11`, min: "1", max: "10", output: "expected to be between 1 and 10 got 11"},
		{input: `"aca"`, min: `"aba"`, max: `"ada"`, output: "undefined"},
		{input: `"ada"`, min: `"aba"`, max: `"aca"`, output: "expected to be between aba and aca got ada"},
	}

	for _, tt := range cases {
		t.Run(fmt.Sprintf("%s_%s_%s_%s", tt.input, tt.min, tt.max, tt.output), func(t *testing.T) {
			o := &strings.Builder{}

			// define the function & call it
			typescript.GenerateValuesToBeBetweenDefinition(o)
			internal.P(o, "console.log(", typescript.GenerateValuesToBeBetweenCall(tt.input, tt.min, tt.max), "?.message)")

			// run the code and assert ouput
			assert.Equal(t, tt.output, runTS(o.String(), true))
		})
	}
}

func TestGenerateValuesToBeInSet_OK(t *testing.T) {
	cases := []struct {
		input  string
		set    string
		output string
	}{
		{input: "1", set: "new Set([1, 2, 3])", output: "undefined"},
		{input: "0", set: "new Set([1, 2, 3])", output: "expected to be in 1,2,3 got 0"},
	}

	for _, tt := range cases {
		t.Run(fmt.Sprintf("%s_%s_%s", tt.input, tt.set, tt.output), func(t *testing.T) {
			o := &strings.Builder{}

			// define the function & call it
			typescript.GenerateValuesToBeInSetDefinition(o)
			internal.P(o, "console.log(", typescript.GenerateValuesToBeInSetCall(tt.input, tt.set), "?.message)")

			// run the code and assert ouput
			assert.Equal(t, tt.output, runTS(o.String(), true))
		})
	}
}

func TestGenerateValueLengthsToBeBetween_Ok(t *testing.T) {
	cases := []struct {
		input  string
		min    string
		max    string
		output string
	}{
		{input: `"abc"`, min: "0", max: "100", output: "undefined"},
		{input: `"abc"`, min: "9", max: "100", output: "expected length to be between between 9 and 100 got 3"},
	}

	for _, tt := range cases {
		t.Run(fmt.Sprintf("%s_%s_%s_%s", tt.input, tt.min, tt.max, tt.output), func(t *testing.T) {
			o := &strings.Builder{}

			// define the function & call it
			typescript.GenerateValueLengthsToBeBetweenDefinition(o)
			internal.P(o, "console.log(", typescript.GenerateValueLengthsToBeBetweenCall(tt.input, tt.min, tt.max), "?.message)")

			// run the code and assert ouput
			assert.Equal(t, tt.output, runTS(o.String(), true))
		})
	}
}

// run some typescript code using ts-node
// returned output is the combinaison of stderr and stdout
// when ts-node exit with an error code we log & fail the test.
func runTS(code string, failOnCompilation bool) string {
	out, err := exec.Command("../../node_modules/.bin/ts-node", "-O", `{"lib": ["dom", "es2015"]}`, "-p", "-e", code).CombinedOutput()

	if failOnCompilation && err != nil {
		fmt.Println(string(out))
		log.Fatal(err)
	}

	return strings.TrimSuffix(string(out), "\nundefined\n")
}
