package docker

import (
	"strconv"

	"context"
	"errors"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/moby/moby/api/types/container"
	"github.com/moby/moby/client"
)

var (
	ReDocker                      = regexp.MustCompile(`^.*docker[-/]([0-9a-z]*)(?:|.*)`)
	ContainerInfo *ContainersInfo = nil
)

func NewContainersInfo() *ContainersInfo {
	ContainerInfo = &ContainersInfo{
		inspectInfo: make(map[string]container.InspectResponse),
		listInfo:    make(map[string]container.Summary),
		topInfo:     make(map[string]container.TopResponse),
		time:        time.Now(),
		inspectLock: sync.RWMutex{},
		topLock:     sync.RWMutex{},
		listLock:    sync.RWMutex{},
	}
	return ContainerInfo
}

type ContainersInfo struct {
	time        time.Time // 记录写入Info的时间
	inspectInfo map[string]container.InspectResponse
	inspectLock sync.RWMutex
	listInfo    map[string]container.Summary
	listLock    sync.RWMutex
	topInfo     map[string]container.TopResponse
	topLock     sync.RWMutex
}

type ContainerPsInfo struct {
	Pid  int32
	Ppid int32
	Uid  string
	Cmd  string
}

func ParsePsInfo(topInfo map[string]container.TopResponse) (map[string][]ContainerPsInfo, error) {
	if topInfo == nil {
		return nil, errors.New("topInfo is nil")
	}
	result := make(map[string][]ContainerPsInfo)
	for cid, topResp := range topInfo {
		indexMap, t := make(map[string]int), 0
		result[cid] = make([]ContainerPsInfo, 0)
		for index, key := range topResp.Titles {
			switch strings.TrimSpace(strings.ToLower(key)) {
			case "pid":
				indexMap["pid"] = index
				t++
			case "ppid":
				indexMap["ppid"] = index
				t++
			case "uid":
				indexMap["uid"] = index
				t++
			case "cmd":
				indexMap["cmd"] = index
				t++
			default:
			}
			if t >= 4 {
				break
			}
		}
		for _, fields := range topResp.Processes {
			item := ContainerPsInfo{}
			if v, ok := indexMap["pid"]; ok {
				pid, err := strconv.ParseInt(fields[v], 10, 64)
				if err != nil {
					return nil, err
				}
				item.Pid = int32(pid)
			}
			if v, ok := indexMap["ppid"]; ok {
				ppid, err := strconv.ParseInt(fields[v], 10, 64)
				if err != nil {
					return nil, err
				}
				item.Ppid = int32(ppid)
			}
			if v, ok := indexMap["uid"]; ok {
				item.Uid = fields[v]
			}
			if v, ok := indexMap["cmd"]; ok {
				item.Cmd = fields[v]
			}
			result[cid] = append(result[cid], item)
		}
	}
	return result, nil
}

func (info *ContainersInfo) Update() error {
	info.listLock.Lock()
	info.topLock.Lock()
	info.inspectLock.Lock()
	defer func() {
		info.inspectLock.Unlock()
		info.topLock.Unlock()
		info.listLock.Unlock()
	}()
	i, s, t, err := getRunningContainerInfo()
	if err != nil {
		return err
	}
	info.inspectInfo = i
	info.listInfo = s
	info.topInfo = t
	info.time = time.Now()
	return nil
}

func (info *ContainersInfo) GetInspectInfo(update bool) (map[string]container.InspectResponse, sync.Locker) {
	if update {
		info.Update()
	}
	rl := info.inspectLock.RLocker()
	rl.Lock()
	if info.inspectInfo == nil {
		return make(map[string]container.InspectResponse), rl
	}
	return info.inspectInfo, rl
}

// GetProcessIdInDocker 获取所用容器的进程信息
func (info *ContainersInfo) GetProcessIdInDocker(update bool) (map[string][]ContainerPsInfo, error) {
	if update {
		err := info.Update()
		if err != nil {
			return nil, err
		}
	}
	rl := info.topLock.RLocker()
	rl.Lock()
	i, err := ParsePsInfo(info.topInfo)
	rl.Unlock()
	rl = nil
	if err != nil {
		return nil, err
	}
	return i, nil
}

// getRunningContainerInfo 获取所有正在运行的docker容器的详细信息
func getRunningContainerInfo() (map[string]container.InspectResponse, map[string]container.Summary, map[string]container.TopResponse, error) {
	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
	if err != nil {
		return nil, nil, nil, err
	}
	defer func() {
		_ = cli.Close()
	}()
	containerSum, err := cli.ContainerList(context.Background(), client.ContainerListOptions{All: false})
	if err != nil {
		return nil, nil, nil, err
	}
	inspects := make(map[string]container.InspectResponse)
	lists := make(map[string]container.Summary)
	tops := make(map[string]container.TopResponse)
	for _, c := range containerSum {
		inspect, innerErr := cli.ContainerInspect(context.Background(), c.ID)
		if innerErr != nil {
			return nil, nil, nil, innerErr
		}
		inspects[c.ID] = inspect
		lists[c.ID] = c
		topInfo, innerErr := cli.ContainerTop(context.Background(), c.ID, nil)
		if innerErr != nil {
			return nil, nil, nil, innerErr
		}
		tops[c.ID] = topInfo
	}
	return inspects, lists, tops, nil
}
