package handler

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/labstack/echo/v4"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	commM "github.com/pingcap/test-infra/common/model"
	"github.com/pingcap/test-infra/common/model/tcms/taskruns"
	"github.com/pingcap/test-infra/services/tcms/utils"
)

func TestGetTestbedSetup(t *testing.T) {
	h := setup(t)
	defer h.Shutdown()

	// not show released

	req := httptest.NewRequest(echo.GET, "/api/v1/testbedsetups/:id", nil)
	rec := httptest.NewRecorder()
	c := utils.NewContext(h.Echo.NewContext(req, rec))
	c.SetPath("/api/v1/testbedsetups/:id")
	c.SetParamNames("id")
	c.SetParamValues("1")
	if !assert.NoError(t, WithAuth(c, h.GetTestbedSetup, 1)) {
		return
	}
	if assert.Equal(t, http.StatusOK, rec.Code) {
		tbs := taskruns.TestbedSetup{}
		assert.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tbs))
		assert.EqualValues(t, 222, *tbs.Spec.Parent.PlanRunID)
	}
}

func TestCreateTestbedSetup(t *testing.T) {
	h := setup(t)
	defer h.Shutdown()

	// not show released
	body := `{
  "apiVersion": "api/v1",
  "kind": "TestbedSetup",
  "metadata": {
    "id": 1234,
    "createAt": "2021-09-22T06:19:20Z",
    "createdBy": "someone@pingcap.com",
    "updateAt": "2021-09-22T06:19:20Z",
    "updateBy": "someone@pingcap.com"
  },
  "spec": {
    "parent": {
      "planRunID": 222
    },
    "testbedRef": {
      "planID": 5678
    },
    "testbedSpec": {
      "generateName": "release-arm64-perftest-sysbench-hybrid-001",
      "resourcePool": "arm64",
      "rmsUrl": "",
      "items": [
        {
          "name": "tiup",
          "type": "WORKLOAD_NODE",
          "spec": {
            "container": {
              "image": "hub.pingcap.net/qa/base-image",
              "imagePullPolicy": "Always",
              "name": "node"
            }
          }
        }
      ],
      "reclaimPolicy": {
        "lease": "24h",
        "gcStrategy": {
          "onFailure": {
            "artifact": {
              "monitor": true,
              "log": true
            }
          }
        }
      }
    },
    "timeout": "2h",
    "priority": 100,
    "outputs": [
      {
        "name": "param1",
        "valueExpr": "{{testbed.items.tc.tidb.dsn}}"
      }
    ]
  },
  "status": {
    "schedule": {
      "scheduleStatus": "pending",
      "scheduleMessage": "insufficient resource: requested {cpu:234}, resource pool 'benchbot' left {cpu: 123}",
      "scheduleTime": "2022-02-15T08:66:23Z",
      "nextScheduleTime": "2022-02-16T09:00:00Z",
      "testbedSpec": null
    },
    "report": {
      "retryCnt": 3,
      "testbedSpec": null,
      "status": "polling"
    },
    "run": {
      "startTime": "2021-09-22T06:19:20Z",
      "completeTime": "2021-09-22T06:24:15Z",
      "phase": "pending",
      "outputs": [
        {
          "name": "testbed",
          "value": "the handle"
        },
        {
          "name": "param1",
          "value": "the dsn"
        }
      ],
      "underlying": {
        "argo": {
          "namespace": "testground",
          "workflow": "flow3",
          "templateName": "plan-2344-223",
          "node": "",
          "nodeUrl": ""
        }
      }
    }
  }
}`

	req := httptest.NewRequest(echo.POST, "/api/v1/testbedsetups", strings.NewReader(body))
	req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
	rec := httptest.NewRecorder()
	c := utils.NewContext(h.Echo.NewContext(req, rec))
	c.SetPath("/api/v1/testbedsetups")
	if !assert.NoError(t, WithAuth(c, h.CreateTestbedSetup, 1)) {
		return
	}
	if assert.Equal(t, http.StatusOK, rec.Code) {
		tbs := taskruns.TestbedSetup{}
		assert.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tbs))
		assert.EqualValues(t, 1234, tbs.MetaData.ID)
	}
}

func TestGenerateScheduleGraph(t *testing.T) {
	h := setup(t)
	defer h.Shutdown()

	req := httptest.NewRequest(echo.GET, "/api/v1/testbedsetups/schedule-graph", nil)
	rec := httptest.NewRecorder()
	c := utils.NewContext(h.Echo.NewContext(req, rec))
	c.SetPath("/api/v1/testbedsetups/schedule-graph")
	require.NoError(t, WithAuth(c, h.GenerateScheduleGraph, 1))
	require.Equal(t, http.StatusOK, rec.Code)
}

func TestGraphBuilder(t *testing.T) {
	input := `
{
    "apiVersion": "api/v1",
    "kind": "TestbedSetupList",
    "items": [
        {
            "apiVersion": "api/v1",
            "kind": "TestbedSetup",
            "metadata": {
                "id": 30007
            },
            "spec": {
                "planExecID": 300007,
                "testbedRef": {
                    "planID": 300007
                }
            },
            "status": {
                "schedule": {
                    "scheduleStatus": "pending",
                    "lastScheduleTime": "2022-03-18T11:39:56.742081+08:00",
                    "scheduleReason": {
                        "schedulerName": "ResourcePoolScheduler",
                        "message": "insufficient resource, requested:{Cpu:161.25c, mem: 1856MiB}"
                    }
                }
            }
        },
        {
            "apiVersion": "api/v1",
            "kind": "TestbedSetup",
            "metadata": {
                "id": 4
            },
            "spec": {
                "planExecID": 330003,
                "testbedRef": {
                    "planID": 330003
                }
            },
            "status": {
                "schedule": {
                    "scheduleStatus": "pending",
                    "lastScheduleTime": "2022-03-18T11:39:56.741979+08:00",
                    "scheduleReason": {
                        "schedulerName": "PriorityScheduler",
                        "message": "blocked by high priority jobs",
                        "relatedSetupIds": [
                            30006, 30007
                        ]
                    }
                }
            }
        }
    ]
}`
	additionalTask := `
{
	"apiVersion": "api/v1",
	"kind": "TestbedSetup",
	"metadata": {
		"id": 30006
	},
	"spec": {
		"planExecID": 300008,
		"testbedRef": {
			"planID": 300008
		}
	},
	"status": {
		"schedule": {
			"scheduleStatus": "done",
			"lastScheduleTime": "2022-03-18T11:39:56.742081+08:00",
			"scheduleReason": {
				"schedulerName": "ResourcePoolScheduler",
				"message": "insufficient resource, requested:{Cpu:161.25c, mem: 1856MiB}"
			}
		}
	}
}`
	list := taskruns.TestbedSetupList{}
	err := json.Unmarshal([]byte(input), &list)
	require.NoError(t, err)
	builder := graphBuilder{}
	output0, err := builder.Compile()
	require.NoError(t, err)
	expected0 := `flowchart LR
nil[( there is no tasks )]
style nil color: gray
`
	require.ElementsMatch(t, strings.Split(expected0, "\n"), strings.Split(output0, "\n"))
	builder.Merge(list.Items)
	lacking := builder.Lacking()
	assert.EqualValues(t, []commM.ID{30006}, lacking)
	output1, err := builder.Compile()
	require.NoError(t, err)
	expected1 := `flowchart LR
subgraph g0[ ]
direction LR
n30007["PlanExecID: 300007<br/>ResourcePoolScheduler: insufficient resource, requested:{Cpu:161.25c, mem: 1856MiB}"]
n30006["SetupID: 30006"]
style n30006 color: gray
end
n4["PlanExecID: 330003<br/>PriorityScheduler: blocked by high priority jobs"]
n4 --> g0
`
	require.ElementsMatch(t, strings.Split(expected1, "\n"), strings.Split(output1, "\n"))
	additionalSetup := taskruns.TestbedSetup{}
	err = json.Unmarshal([]byte(additionalTask), &additionalSetup)
	require.NoError(t, err)
	builder.Merge([]taskruns.TestbedSetup{additionalSetup})
	output2, err := builder.Compile()
	require.NoError(t, err)
	expected := `flowchart LR
subgraph g0[ ]
direction LR
n30007["PlanExecID: 300007<br/>ResourcePoolScheduler: insufficient resource, requested:{Cpu:161.25c, mem: 1856MiB}"]
n30006["PlanExecID: 300008(not Pending)"]
style n30006 color: gray
end
n4["PlanExecID: 330003<br/>PriorityScheduler: blocked by high priority jobs"]
n4 --> g0
`
	require.ElementsMatch(t, strings.Split(expected, "\n"), strings.Split(output2, "\n"))
}
