// Copyright 2016-2021, Pulumi Corporation.
//
// 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 docs

import (
	"encoding/json"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
)

func TestGeneratePackageTree(t *testing.T) {
	t.Parallel()

	dctx := newDocGenContext()
	testPackageSpec := newTestPackageSpec()

	schemaPkg, err := schema.ImportSpec(testPackageSpec, nil)
	assert.NoError(t, err, "importing spec")

	dctx.initialize(unitTestTool, schemaPkg)
	pkgTree, err := dctx.generatePackageTree()
	if err != nil {
		t.Errorf("Error generating the package tree for package %s: %v", schemaPkg.Name, err)
	}

	assert.NotEmpty(t, pkgTree, "Package tree was empty")

	t.Run("ValidatePackageTreeTopLevelItems", func(t *testing.T) {
		t.Parallel()

		assert.Equal(t, entryTypeModule, pkgTree[0].Type)
		assert.Equal(t, entryTypeModule, pkgTree[1].Type)
		assert.Equal(t, entryTypeResource, pkgTree[2].Type)
		assert.Equal(t, entryTypeResource, pkgTree[3].Type)
		assert.Equal(t, entryTypeFunction, pkgTree[4].Type)
	})

	t.Run("ValidateSortOrder", func(t *testing.T) {
		t.Parallel()

		assert.Equal(t, "module", pkgTree[0].Name)
		assert.Equal(t, "module2", pkgTree[1].Name)
		assert.Equal(t, "PackageLevelResource", pkgTree[2].Name)
		assert.Equal(t, "Provider", pkgTree[3].Name)
		assert.Equal(t, "getPackageResource", pkgTree[4].Name)
	})

	t.Run("ValidatePackageTreeModuleChildren", func(t *testing.T) {
		t.Parallel()

		assert.Equal(t, 2, len(pkgTree[0].Children))
		children := pkgTree[0].Children
		assert.Equal(t, entryTypeResource, children[0].Type)
		assert.Equal(t, entryTypeFunction, children[1].Type)
	})
}

// Original issues: pulumi/pulumi#14821, pulumi/pulumi#14820.
func TestGeneratePackageTreeNested(t *testing.T) {
	t.Parallel()

	type testCase struct {
		name   string
		spec   schema.PackageSpec
		expect string
	}

	testCases := []testCase{
		{
			"index top module",
			schema.PackageSpec{
				Name:    "testindex",
				Version: "0.0.1",
				Resources: map[string]schema.ResourceSpec{
					"testindex:index:Resource": {},
				},
			},
			`[
			 {
			   "name": "Provider",
			   "type": "resource",
			   "link": "provider"
			 },
			 {
			   "name": "Resource",
			   "type": "resource",
			   "link": "resource"
			 }
		       ]`,
		},
		{
			"random",
			schema.PackageSpec{
				Name:    "random",
				Version: "0.0.1",
				Resources: map[string]schema.ResourceSpec{
					"random:index/randomId:RandomId":             {},
					"random:index/randomPassword:RandomPassword": {},
				},
			},
			`
			 [
			  {
			    "name": "Provider",
			    "type": "resource",
			    "link": "provider"
			  },
			  {
			    "name": "RandomId",
			    "type": "resource",
			    "link": "randomid"
			  },
			  {
			    "name": "RandomPassword",
			    "type": "resource",
			    "link": "randompassword"
			  }
			]`,
		},
		{
			"14820",
			schema.PackageSpec{
				Name:    "fortios",
				Version: "0.0.1",
				Resources: map[string]schema.ResourceSpec{
					"fortios:router/bgp:Bgp":             {},
					"fortios:router/bgp/network:Network": {},
				},
			},
			`
			[
			  {
			    "name": "router",
			    "type": "module",
			    "link": "router/",
			    "children": [
			      {
				"name": "bgp",
				"type": "module",
				"link": "bgp/",
				"children": [
				  {
				    "name": "Network",
				    "type": "resource",
				    "link": "network"
				  }
				]
			      },
			      {
				"name": "Bgp",
				"type": "resource",
				"link": "bgp"
			      }
			    ]
			  },
			  {
			    "name": "Provider",
			    "type": "resource",
			    "link": "provider"
			  }
			]`,
		},
		{
			"14821",
			schema.PackageSpec{
				Name:    "fortios",
				Version: "0.0.1",
				Resources: map[string]schema.ResourceSpec{
					"fortios:log/syslogd/v2/filter:Filter":                   {},
					"fortios:log/syslogd/v2/overridefilter:Overridefilter":   {},
					"fortios:log/syslogd/v2/overridesetting:Overridesetting": {},
					"fortios:log/syslogd/v2/setting:Setting":                 {},
				},
			},
			`
			[
			  {
			    "name": "log",
			    "type": "module",
			    "link": "log/",
			    "children": [
			      {
				"name": "syslogd",
				"type": "module",
				"link": "syslogd/",
				"children": [
				  {
				    "name": "v2",
				    "type": "module",
				    "link": "v2/",
				    "children": [
				      {
					"name": "Filter",
					"type": "resource",
					"link": "filter"
				      },
				      {
					"name": "Overridefilter",
					"type": "resource",
					"link": "overridefilter"
				      },
				      {
					"name": "Overridesetting",
					"type": "resource",
					"link": "overridesetting"
				      },
				      {
					"name": "Setting",
					"type": "resource",
					"link": "setting"
				      }
				    ]
				  }
				]
			      }
			    ]
			  },
			  {
			    "name": "Provider",
			    "type": "resource",
			    "link": "provider"
			  }
			]`,
		},
	}

	prep := func(t *testing.T, tc testCase) (*docGenContext, *schema.Package) {
		t.Helper()

		schemaPkg, err := schema.ImportSpec(tc.spec, nil)
		assert.NoError(t, err, "importing spec")

		c := newDocGenContext()
		c.initialize("test", schemaPkg)

		return c, schemaPkg
	}

	for _, tc := range testCases {
		tc := tc

		for _, mv := range []string{"", "(.*)(?:/[^/]*)"} {
			name := tc.name
			if mv != "" {
				tc.spec.Meta = &schema.MetadataSpec{
					ModuleFormat: mv,
				}
				name += "-withModuleFormat"
			} else {
				name += "-withoutModuleFormat"
			}
			t.Run(name, func(t *testing.T) {
				t.Parallel()

				c, _ := prep(t, tc)

				items, err := c.generatePackageTree()
				require.NoError(t, err)

				data, err := json.MarshalIndent(items, "", "  ")
				require.NoError(t, err)

				t.Logf("%s", string(data))

				require.JSONEq(t, tc.expect, string(data))
			})

			t.Run(name+"/generatePackage", func(t *testing.T) {
				t.Parallel()

				c, schemaPkg := prep(t, tc)

				files, err := c.generatePackage("test", schemaPkg)
				require.NoError(t, err)

				for f := range files {
					t.Logf("+ %v", f)
				}
			})
		}
	}
}