package nodejs

import (
	"encoding/json"
	"os"
	"path/filepath"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/pulumi/pulumi/pkg/v3/codegen"
	"github.com/pulumi/pulumi/pkg/v3/codegen/testing/test"
	"github.com/pulumi/pulumi/pkg/v3/testing/integration"
)

func Check(t *testing.T, path string, dependencies codegen.StringSet, linkLocal bool) {
	dir := filepath.Dir(path)

	removeFile := func(name string) {
		path := filepath.Join(dir, name)
		if err := os.Remove(path); !os.IsNotExist(err) {
			require.NoError(t, err, "Failed to delete '%s'", path)
		}
	}

	// We delete and regenerate package files for each run.
	removeFile("yarn.lock")
	removeFile("package.json")
	removeFile("tsconfig.json")

	// Write out package.json
	pkgs := nodejsPackages(t, dependencies)
	pkgInfo := npmPackage{
		Dependencies: map[string]string{
			"@pulumi/pulumi": "latest",
		},
		DevDependencies: map[string]string{
			"@types/node": "^17.0.14",
			"typescript":  "^4.5.5",
		},
	}
	for pkg, v := range pkgs {
		pkgInfo.Dependencies[pkg] = v
	}
	pkgJSON, err := json.MarshalIndent(pkgInfo, "", "    ")
	require.NoError(t, err)
	err = os.WriteFile(filepath.Join(dir, "package.json"), pkgJSON, 0o600)
	require.NoError(t, err)

	tsConfig := map[string]string{}
	tsConfigJSON, err := json.MarshalIndent(tsConfig, "", "    ")
	require.NoError(t, err)
	err = os.WriteFile(filepath.Join(dir, "tsconfig.json"), tsConfigJSON, 0o600)
	require.NoError(t, err)

	TypeCheck(t, path, dependencies, linkLocal)
}

func TypeCheck(t *testing.T, path string, _ codegen.StringSet, linkLocal bool) {
	dir := filepath.Dir(path)

	typeCheckGeneratedPackage(t, dir, linkLocal)
}

func typeCheckGeneratedPackage(t *testing.T, pwd string, linkLocal bool) {
	test.RunCommand(t, "npm_install", pwd, "npm", "install")
	if linkLocal {
		test.RunCommand(t, "yarn_link", pwd, "yarn", "link", "@pulumi/pulumi")
	}
	tscOptions := &integration.ProgramTestOptions{
		// Avoid Out of Memory error on CI:
		Env: []string{"NODE_OPTIONS=--max_old_space_size=4096"},
	}
	test.RunCommandWithOptions(t, tscOptions, "tsc", pwd, filepath.Join(pwd, "node_modules", ".bin", "tsc"),
		"--noEmit", "--skipLibCheck", "true", "--skipDefaultLibCheck", "true")
}

// Returns the nodejs equivalent to the hcl2 package names provided.
func nodejsPackages(t *testing.T, deps codegen.StringSet) map[string]string {
	result := make(map[string]string, len(deps))
	for _, d := range deps.SortedValues() {
		pkgName := "@pulumi/" + d
		set := func(pkgVersion string) {
			result[pkgName] = "^" + pkgVersion
		}
		switch d {
		case "aws":
			set(test.AwsSchema)
		case "azure-native":
			set(test.AzureNativeSchema)
		case "azure":
			set(test.AzureSchema)
		case "kubernetes":
			set(test.KubernetesSchema)
		case "random":
			set(test.RandomSchema)
		case "eks":
			set(test.EksSchema)
		case "aws-static-website":
			set(test.AwsStaticWebsiteSchema)
		case "aws-native":
			set(test.AwsNativeSchema)
		default:
			t.Logf("Unknown package requested: %s", d)
		}

	}
	return result
}

func GenerateProgramBatchTest(t *testing.T, testCases []test.ProgramTest) {
	test.TestProgramCodegen(t,
		test.ProgramCodegenOptions{
			Language:   "nodejs",
			Extension:  "ts",
			OutputFile: "index.ts",
			Check: func(t *testing.T, path string, dependencies codegen.StringSet) {
				Check(t, path, dependencies, true)
			},
			GenProgram: GenerateProgram,
			TestCases:  testCases,
		})
}