package github_test

import (
	"dataservices/commons/github"
	"dataservices/internal/entity"
	"encoding/json"
	"testing"

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

/*
the test use following folder structure
root
- f1.txt
- d1
  - f2.txt
*/
func buildTestFileTree() *github.FileTreeNode {
	root := github.NewDirectoryNode("root")
	f1 := github.NewFileNode("f1.txt", []byte("this is f1"))
	d1 := github.NewDirectoryNode("d1")
	root.AppendChild(f1)
	root.AppendChild(d1)
	f2 := github.NewFileNode("f2.txt", []byte("this if f2"))
	d1.AppendChild(f2)
	return root
}

func buildTestSnapshot() *entity.DataAppSnapshot {
	return &entity.DataAppSnapshot{
		DataApp: &entity.DataApp{
			ExternalAppID: "dataapi-pzEXbKhB",
			AppName:       "multi-region-test",
			AppType:       "dataapi",
		},
		Endpoints: []*entity.Endpoint{
			{
				Name:        "/hello_world_copy",
				Description: "jdkajkr3hri3",
				Endpoint:    "/hello/world/good/night",
				Method:      "GET",
				ClusterId:   728278342718,
				Args:        `[{"name":"limit","type":"number","value_type":"","value_map":null,"required":0,"default":"10","description":"","pattern":""}]`,
				Settings:    `{"timeout":30000,"row_limit":1000}`,
				SqlTemplate: `/* Getting Started:
Enter "USE {database};" before entering your SQL statements.
Type "--your question" + Enter to try out AI-generated SQL queries
Declare a parameter like "Where id = ${arg}".
*/
SELECT * from github_events limit ${limit}
`,
			},
			{
				Name:        "/hello",
				Description: "jdkajkr3hri3",
				Endpoint:    "/hello/world/3",
				Method:      "GET",
				ClusterId:   728278342718,
				Args:        `[{"name":"limit","type":"number","value_type":"","value_map":null,"required":0,"default":"10","description":"","pattern":""}]`,
				Settings:    `{"timeout":30000,"row_limit":1000}`,
				SqlTemplate: `/* Getting Started:
Enter "USE {database};" before entering your SQL statements.
Type "--your question" + Enter to try out AI-generated SQL queries
Declare a parameter like "Where id = ${arg}".
*/
abcdd;
SELECT * from hahaha limit ${limit}
`,
			},
		},
		Clusters: []*entity.AppCluster{{
			ClusterID: 728278342718,
		}},
	}
}

func TestTraverse(t *testing.T) {
	root := buildTestFileTree()
	files := root.TraverseFiles()
	assert.Equal(t, 2, len(files))
	assert.Equal(t, "/d1/f2.txt", files[0].Path())
	assert.Equal(t, "/f1.txt", files[1].Path())
}

func TestAppendRelativePathFile(t *testing.T) {
	root := github.NewDirectoryNode("root")
	err := root.AppendRelativePathFile("f1.txt", []byte(""))
	assert.Nil(t, err)
	err = root.AppendRelativePathFile("/d1/f2.txt", []byte(""))
	assert.Nil(t, err)
	err = root.AppendRelativePathFile("/d1/f3.txt", []byte(""))
	assert.Nil(t, err)
	files := root.TraverseFiles()
	assert.Equal(t, 3, len(files))
	assert.Equal(t, "/d1/f2.txt", files[0].Path())
	assert.Equal(t, "/d1/f3.txt", files[1].Path())
	assert.Equal(t, "/f1.txt", files[2].Path())
}

func TestAppendOrder(t *testing.T) {
	root := github.NewDirectoryNode("root")
	f1 := github.NewFileNode("f1.txt", []byte("this is f1"))
	f2 := github.NewFileNode("f2.txt", []byte("this is f2"))
	root.AppendChild(f1)
	root.AppendChild(f2)
	files := root.TraverseFiles()
	assert.Equal(t, 2, len(files))
	assert.Equal(t, "/f1.txt", files[0].Path())
	assert.Equal(t, "/f2.txt", files[1].Path())

	newRoot := github.NewDirectoryNode("root")
	newRoot.AppendChild(f2)
	newRoot.AppendChild(f1)
	newFiles := newRoot.TraverseFiles()
	assert.Equal(t, 2, len(files))
	assert.Equal(t, "/f1.txt", newFiles[0].Path())
	assert.Equal(t, "/f2.txt", newFiles[1].Path())
}

func TestSameNode(t *testing.T) {
	root1 := github.NewDirectoryNode("root")
	root2 := github.NewDirectoryNode("root")

	diff := root1.Diff(root2)
	assert.Equal(t, 0, len(diff.Added))
	assert.Equal(t, 0, len(diff.Deleted))
	assert.Equal(t, 0, len(diff.Modified))

	diff = root1.Diff(root1)
	assert.Equal(t, 0, len(diff.Added))
	assert.Equal(t, 0, len(diff.Deleted))
	assert.Equal(t, 0, len(diff.Modified))
}

func TestBasicDiffAddDeleteModify(t *testing.T) {
	root1 := buildTestFileTree()
	root2 := github.NewDirectoryNode("root")
	// modify
	f1 := github.NewFileNode("f1.txt", []byte("this is f1-1"))
	// delete and add
	d1 := github.NewDirectoryNode("t2")
	root2.AppendChild(f1)
	root2.AppendChild(d1)
	f2 := github.NewFileNode("f2.txt", []byte("this if f2"))
	d1.AppendChild(f2)

	diff := root1.Diff(root2)
	assert.Equal(t, 1, len(diff.Added))
	assert.Equal(t, 1, len(diff.Deleted))
	assert.Equal(t, 1, len(diff.Modified))

	diffText, err := diff.ToString()
	assert.Nil(t, err)
	assert.Equal(t, "--- /t2/f2.txt\n+++ /t2/f2.txt\n@@ -0,0 +1 @@\n+this if f2\n--- /d1/f2.txt\n+++ /d1/f2.txt\n@@ -1 +0,0 @@\n-this if f2\n--- /f1.txt\n+++ /f1.txt\n@@ -1 +1 @@\n-this is f1\n+this is f1-1\n", diffText)
}

func TestSnapshot2FileTreeNode(t *testing.T) {
	snapshot := buildTestSnapshot()
	fileTree, err := github.Snapshot2FileTreeNode(snapshot)
	assert.Nil(t, err)
	emptyTree := github.NewDirectoryNode("root")
	diff := emptyTree.Diff(fileTree)
	diffText, err := diff.ToString()
	assert.Nil(t, err)
	assert.Equal(t, "--- /data_sources/cluster.json\n+++ /data_sources/cluster.json\n@@ -0,0 +1,5 @@\n+[\n+  {\n+    \"cluster_id\": 728278342718\n+  }\n+]\n--- /dataapp_config.json\n+++ /dataapp_config.json\n@@ -0,0 +1,5 @@\n+{\n+  \"app_id\": \"dataapi-pzEXbKhB\",\n+  \"app_name\": \"multi-region-test\",\n+  \"app_type\": \"dataapi\"\n+}\n--- /http_endpoints/config.json\n+++ /http_endpoints/config.json\n@@ -0,0 +1,58 @@\n+[\n+  {\n+    \"name\": \"/hello_world_copy\",\n+    \"description\": \"jdkajkr3hri3\",\n+    \"method\": \"GET\",\n+    \"endpoint\": \"/hello/world/good/night\",\n+    \"data_source\": {\n+      \"cluster_id\": 728278342718\n+    },\n+    \"params\": [\n+      {\n+        \"name\": \"limit\",\n+        \"type\": \"number\",\n+        \"value_type\": \"\",\n+        \"value_map\": null,\n+        \"required\": 0,\n+        \"default\": \"10\",\n+        \"description\": \"\",\n+        \"pattern\": \"\"\n+      }\n+    ],\n+    \"settings\": {\n+      \"timeout\": 30000,\n+      \"row_limit\": 1000\n+    },\n+    \"sql_file\": \"sql/GET-hello-world-good-night.sql\",\n+    \"type\": \"sql_endpoint\",\n+    \"return_type\": \"json\"\n+  },\n+  {\n+    \"name\": \"/hello\",\n+    \"description\": \"jdkajkr3hri3\",\n+    \"method\": \"GET\",\n+    \"endpoint\": \"/hello/world/3\",\n+    \"data_source\": {\n+      \"cluster_id\": 728278342718\n+    },\n+    \"params\": [\n+      {\n+        \"name\": \"limit\",\n+        \"type\": \"number\",\n+        \"value_type\": \"\",\n+        \"value_map\": null,\n+        \"required\": 0,\n+        \"default\": \"10\",\n+        \"description\": \"\",\n+        \"pattern\": \"\"\n+      }\n+    ],\n+    \"settings\": {\n+      \"timeout\": 30000,\n+      \"row_limit\": 1000\n+    },\n+    \"sql_file\": \"sql/GET-hello-world-3.sql\",\n+    \"type\": \"sql_endpoint\",\n+    \"return_type\": \"json\"\n+  }\n+]\n--- /http_endpoints/sql/GET-hello-world-3.sql\n+++ /http_endpoints/sql/GET-hello-world-3.sql\n@@ -0,0 +1,8 @@\n+/* Getting Started:\n+Enter \"USE {database};\" before entering your SQL statements.\n+Type \"--your question\" + Enter to try out AI-generated SQL queries\n+Declare a parameter like \"Where id = ${arg}\".\n+*/\n+abcdd;\n+SELECT * from hahaha limit ${limit}\n+\n--- /http_endpoints/sql/GET-hello-world-good-night.sql\n+++ /http_endpoints/sql/GET-hello-world-good-night.sql\n@@ -0,0 +1,7 @@\n+/* Getting Started:\n+Enter \"USE {database};\" before entering your SQL statements.\n+Type \"--your question\" + Enter to try out AI-generated SQL queries\n+Declare a parameter like \"Where id = ${arg}\".\n+*/\n+SELECT * from github_events limit ${limit}\n+\n", diffText)
}

func TestFileTreeNode2Snapshot(t *testing.T) {
	oldSnapshot := buildTestSnapshot()
	fileTree, err := github.Snapshot2FileTreeNode(oldSnapshot)
	assert.Nil(t, err)
	newSnapshot, err := github.FileTreeNode2Snapshot(fileTree)
	assert.Nil(t, err)
	oldJson, err := json.Marshal(oldSnapshot)
	assert.Nil(t, err)
	newJson, err := json.Marshal(newSnapshot)
	assert.Nil(t, err)
	assert.Equal(t, string(oldJson), string(newJson))
}
