// Copyright 2025 The HuaTuo Authors
//
// 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 pod

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"huatuo-bamai/internal/conf"
	"huatuo-bamai/internal/utils/pidutil"

	dockertypes "github.com/docker/docker/api/types"
	dockerclient "github.com/docker/docker/client"
	corev1 "k8s.io/api/core/v1"
	k8sremote "k8s.io/cri-client/pkg"
)

const (
	containerEnvNoop = iota
	containerEnvDocker
	containerEnvContainerd
)

var (
	// detect whether the current environment is Docker or containerd.
	currContainerEnv int

	// Docker Root Dir from `docker info`
	dockerRootDir string
	// Containerd State Dir. More information see https://github.com/containerd/containerd/blob/main/docs/cri/config.md
	containerdStateDir           string
	initContainerProviderEnvOnce sync.Once
)

func initContainerProviderEnv(containerEnv string) {
	initContainerProviderEnvOnce.Do(func() {
		var err error
		switch containerEnv {
		case "docker":
			err = initDockerProviderEnv()
		case "containerd":
			err = initContainerdProviderEnv()
		default:
			err = fmt.Errorf("invalid the container provider: %s", containerEnv)
		}

		if err != nil {
			panic(err)
		}
	})
}

func initDockerProviderEnv() error {
	client, err := dockerclient.NewClientWithOpts(dockerclient.FromEnv, dockerclient.WithVersion(conf.Get().Pod.DockerAPIVersion))
	if err != nil {
		return fmt.Errorf("create docker client, err: %w", err)
	}
	defer client.Close()

	// timeout: 5s
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	info, err := client.Info(ctx)
	if err != nil {
		return fmt.Errorf("get docker info, err: %w", err)
	}

	dockerRootDir = info.DockerRootDir
	currContainerEnv = containerEnvDocker
	return nil
}

func initContainerdProviderEnv() error {
	// timeout: 5s
	client, err := k8sremote.NewRemoteRuntimeService("unix:///run/containerd/containerd.sock", 5*time.Second, nil, nil)
	if err != nil {
		return fmt.Errorf("create containerd client, err: %w", err)
	}

	// timeout: 5s
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	status, err := client.Status(ctx, true)
	if err != nil {
		return fmt.Errorf("get containerd status, err: %w", err)
	}

	config := struct {
		StateDir string `json:"stateDir"`
	}{}
	if err := json.Unmarshal([]byte(status.Info["config"]), &config); err != nil {
		return fmt.Errorf("unmarshal containerd config, err: %w", err)
	}

	containerdStateDir = path.Dir(config.StateDir)
	currContainerEnv = containerEnvContainerd
	return nil
}

func containerInitPid(containerID string) (int, error) {
	switch currContainerEnv {
	case containerEnvDocker:
		return containerInitPidInDocker(containerID)
	case containerEnvContainerd:
		return containerInitPidInContainerd(containerID)
	default:
		return -1, fmt.Errorf("invalid container env")
	}
}

func containerInitPidInDocker(containerID string) (int, error) {
	configPath := filepath.Join(dockerRootDir, "containers", containerID, "config.v2.json")
	content, err := os.ReadFile(configPath)
	if err != nil {
		return -1, err
	}

	container := dockertypes.ContainerJSON{}
	if err := json.Unmarshal(content, &container); err != nil {
		return -1, err
	}

	if container.State.Pid == 0 {
		return -1, fmt.Errorf("invalid pid for container %s", containerID)
	}
	return container.State.Pid, nil
}

func containerInitPidInContainerd(containerID string) (int, error) {
	// pid: $state/io.containerd.runtime.v2.task/k8s.io/$container/init.pid
	// runtime runc v2?
	// kata ?
	filePath := filepath.Join(containerdStateDir, "io.containerd.runtime.v2.task", "k8s.io", containerID, "init.pid")

	return pidutil.Read(filePath)
}

func containerCgroupSuffix(containerID string, pod *corev1.Pod) string {
	if pod.Status.QOSClass == corev1.PodQOSGuaranteed {
		return fmt.Sprintf("/kubepods/pod%s/%s", pod.UID, containerID)
	}

	return fmt.Sprintf("/kubepods/%s/pod%s/%s", strings.ToLower(string(pod.Status.QOSClass)), pod.UID, containerID)
}
