package framework

import (
	"fmt"
	"io"
	"net/url"
	"time"

	"k8s.io/apimachinery/pkg/util/wait"

	apiresource "github.com/pingcap/test-infra/common/model/resource"
	"github.com/pingcap/test-infra/sdk/resource"
	"github.com/pingcap/test-infra/sdk/resource/impl/k8s"
)

func (f *Framework) CreateWorkloadNode(testbed *k8s.Testbed) *k8s.WorkloadNode {
	client := k8s.NewWorkloadNodeClient(f.KubeClientSetPack)

	r, err := client.Create([]byte(fmt.Sprintf(`{
  "name":"alpineblock",
  "testbed":"%s",
  "type":"%s",
  "spec":{
    "container":{
      "name":"alpineblock",
      "image":"alpine:latest",
      "command":[
        "tail",
        "-f",
        "/dev/null"
      ],
      "resources":{
        "requests":{
          "memory":"64Mi",
          "cpu":"250m",
          "storage":"1Gi"
        }
      }
    },
    "artifact": {
        "artifactPaths": ["/tmp/node.log"]
    }
  }
}
`, testbed.Name, apiresource.ResourceTypeWorkloadNode)))

	ExpectNoError(err)
	return r.(*k8s.WorkloadNode)
}

func (f *Framework) CreateWorkloadNodeWithStorageClass(testbed *k8s.Testbed, storageClassName string, mountPath string) *k8s.WorkloadNode {
	client := k8s.NewWorkloadNodeClient(f.KubeClientSetPack)

	r, err := client.Create([]byte(fmt.Sprintf(`{
  "name":"alpineblock",
  "testbed":"%s",
  "type":"%s",
  "spec":{
    "container":{
      "name":"alpineblock",
      "image":"alpine:latest",
      "command":[
        "tail",
        "-f",
        "/dev/null"
      ],
      "resources":{
        "requests":{
          "memory":"64Mi",
          "cpu":"250m",
          "storage":"1Gi"
        }
      },
      "storageClassName": "%s",
      "volumeMount": {
        "mountPath": "%s"
      }
    }
  }
}
`, testbed.Name, apiresource.ResourceTypeWorkloadNode, storageClassName, mountPath)))

	ExpectNoError(err)
	return r.(*k8s.WorkloadNode)
}

func (f *Framework) CreateWorkloadNodeWithNodeSelector(testbed *k8s.Testbed, hostName string) *k8s.WorkloadNode {
	client := k8s.NewWorkloadNodeClient(f.KubeClientSetPack)
	r, err := client.Create([]byte(fmt.Sprintf(`{
    "name":"alpineblock",
    "testbed":"%s",
    "type":"%s",
    "spec":{
      "container":{
        "name":"alpineblock",
        "image":"alpine:latest",
        "command":[
          "tail",
          "-f",
          "/dev/null"
        ],
        "resources":{
          "requests":{
            "memory":"64Mi",
            "cpu":"250m",
            "storage":"1Gi"
          }
        },
        "nodeSelector":{
          "kubernetes.io/hostname": "%s"
        }
      }
    }
  }
  `, testbed.Name, apiresource.ResourceTypeWorkloadNode, hostName)))

	ExpectNoError(err)
	return r.(*k8s.WorkloadNode)
}

func (f *Framework) CreateWorkloadNodeWithVolumeMount(testbed *k8s.Testbed, mountPath string) *k8s.WorkloadNode {
	client := k8s.NewWorkloadNodeClient(f.KubeClientSetPack)

	r, err := client.Create([]byte(fmt.Sprintf(`{
  "name":"alpineblock",
  "testbed":"%s",
  "type":"%s",
  "spec":{
    "container":{
      "name":"alpineblock",
      "image":"alpine:latest",
      "command":[
        "tail",
        "-f",
        "/dev/null"
      ],
      "resources":{
        "requests":{
          "memory":"64Mi",
          "cpu":"250m",
          "storage":"1Gi"
        }
      },
      "volumeMount": {
        "mountPath": "%s"
      }
    }
  }
}
`, testbed.Name, apiresource.ResourceTypeWorkloadNode, mountPath)))

	ExpectNoError(err)
	return r.(*k8s.WorkloadNode)
}

func (f *Framework) CreateWorkloadNodeWithNodePort(testbed *k8s.Testbed, port string) *k8s.WorkloadNode {
	client := k8s.NewWorkloadNodeClient(f.KubeClientSetPack)

	r, err := client.Create([]byte(fmt.Sprintf(`{
  "name":"nginx",
  "testbed":"%s",
  "type":"%s",
  "spec":{
    "container":{
      "name":"nginx",
      "image":"nginx:latest",
      "resources":{
        "requests":{
          "memory":"64Mi",
          "cpu":"250m"
        }
      }
    },
    "loadBalancer": {
      "containerPort": %s,
      "serviceType": "NodePort"
    }
  }
}
`, testbed.Name, apiresource.ResourceTypeWorkloadNode, port)))

	ExpectNoError(err)
	return r.(*k8s.WorkloadNode)
}

func (f *Framework) CreateWorkloadNodeWithLabel(testbed *k8s.Testbed, labelKey, labelValue string) *k8s.WorkloadNode {
	client := k8s.NewWorkloadNodeClient(f.KubeClientSetPack)

	r, err := client.Create([]byte(fmt.Sprintf(`{
  "name":"nginx",
  "testbed":"%s",
  "type":"%s",
  "spec":{
    "container":{
      "name":"nginx",
      "image":"nginx:latest",
      "resources":{
        "requests":{
          "memory":"64Mi",
          "cpu":"250m"
        }
      },
      "labels": {
        "%s": "%s"
      }
    }
  }
}
`, testbed.Name, apiresource.ResourceTypeWorkloadNode, labelKey, labelValue)))

	ExpectNoError(err)
	return r.(*k8s.WorkloadNode)
}

func (f *Framework) CreateWorkloadNodeWithArtifactPath(testbed *k8s.Testbed, artifactPath string) *k8s.WorkloadNode {
	client := k8s.NewWorkloadNodeClient(f.KubeClientSetPack)

	r, err := client.Create([]byte(fmt.Sprintf(`{
  "name":"alpineblock",
  "testbed":"%s",
  "type":"%s",
  "spec":{
    "artifact": {
      "artifactPaths": ["%s"]
    },
    "container":{
      "name":"alpineblock",
      "image":"alpine:latest",
      "command":[
        "tail",
        "-f",
        "/dev/null"
      ],
      "resources":{
        "requests":{
          "memory":"64Mi",
          "cpu":"250m",
          "storage":"1Gi"
        }
      }
    }
  }
}
`, testbed.Name, apiresource.ResourceTypeWorkloadNode, artifactPath)))

	ExpectNoError(err)
	return r.(*k8s.WorkloadNode)
}

func (f *Framework) CreateWorkloadNodeWithAdditionalVolume(testbed *k8s.Testbed, volumeMountPath string) *k8s.WorkloadNode {
	client := k8s.NewWorkloadNodeClient(f.KubeClientSetPack)
	r, err := client.Create([]byte(fmt.Sprintf(`{
    "name": "alpineblock",
    "testbed": "%s",
    "type": "%s",
    "spec": {
      "container": {
        "name": "alpineblock",
        "image": "alpine:latest",
        "command": [
          "tail",
          "-f",
          "/dev/null"
        ],
        "additionalVolumes": [
          {
            "volumeMountPath": "%s",
            "volumeSource": {
              "persistentVolume": {
                "persistentVolumeName": "task-pv-volume",
                "storageClassName": "test-infra-debug"
              }
            }
          }
        ]
      }
    }
  }
  `, testbed.Name, apiresource.ResourceTypeWorkloadNode, volumeMountPath)))

	ExpectNoError(err)
	return r.(*k8s.WorkloadNode)
}

func (f *Framework) CreateTiUPWorkloadNode(testbed *k8s.Testbed) *k8s.WorkloadNode {
	client := k8s.NewWorkloadNodeClient(f.KubeClientSetPack)

	r, err := client.Create([]byte(fmt.Sprintf(`{
  "name":"tiup",
  "testbed":"%s",
  "type":"%s",
  "spec":{
    "container":{
      "name":"node",
      "image":"hub.pingcap.net/qa/base-image",
      "resources":{
        "requests":{
          "memory":"2Gi",
          "cpu":"2",
          "storage":"1Gi"
        }
      },
      "securityContext": {"capabilities": {"add": ["SYS_ADMIN", "SYS_PTRACE", "NET_ADMIN"]}}
    }
  }
}
`, testbed.Name, apiresource.ResourceTypeWorkloadNode)))

	ExpectNoError(err)
	return r.(*k8s.WorkloadNode)
}

func (f *Framework) GetWorkloadNode(handle apiresource.ResourceHandle) *k8s.WorkloadNode {
	client := k8s.NewWorkloadNodeClient(f.KubeClientSetPack)
	r, err := client.Get(handle)
	ExpectNoError(err)
	return r.(*k8s.WorkloadNode)
}

func (f *Framework) GetWorkloadNodeEndpoints(handle apiresource.ResourceHandle) apiresource.ResourceEndpoints {
	client := k8s.NewWorkloadNodeClient(f.KubeClientSetPack)
	r, err := client.GetEndpoints(handle)
	ExpectNoError(err)
	return r
}

func (f *Framework) ArchiveWorkloadNode(handle apiresource.ResourceHandle, archiver func(objectName string, reader io.Reader) (*url.URL, error)) {
	client := k8s.NewWorkloadNodeClient(f.KubeClientSetPack)
	_, err := client.Artifact(handle, apiresource.GcArtifactAction{Log: true}, archiver)
	ExpectNoError(err)
}

func (f *Framework) DeleteWorkloadNodeWaited(node *k8s.WorkloadNode) {
	client := k8s.NewWorkloadNodeClient(f.KubeClientSetPack)
	ExpectNoError(client.Delete(node.ResourceHandle))

	err := wait.PollImmediate(poll, f.DeletionTimeout, func() (done bool, err error) {
		_, err = client.Get(node.ResourceHandle)
		if resource.IsResourceNotFoundError(err) {
			return true, nil
		}
		return false, nil
	})
	ExpectNoError(err)
}

func (f *Framework) WaitForWorkloadNodeReady(node *k8s.WorkloadNode, timeout time.Duration) error {
	if err := wait.PollImmediate(poll, timeout, func() (done bool, err error) {
		if err := node.Sync(); err != nil {
			return false, err
		}
		return node.Ready(), nil
	}); err != nil {
		return fmt.Errorf("wait for workload node ready occur an error: %v", err)
	}
	return nil
}

func (f *Framework) ArtifactWorkloadNode(node *k8s.WorkloadNode, archiver resource.ArchiverFunc) {
	client := k8s.NewWorkloadNodeClient(f.KubeClientSetPack)
	_, err := client.Artifact(node.Handle(), apiresource.GcArtifactAction{Monitor: true, Log: true}, archiver)
	ExpectNoError(err)
}
