package containers

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/rosti-cz/node-api/apps"
)

// Return bytes, inodes occupied by a directory and/or error if there is any
func du(path string) (int, int, error) {
	space, inodes := 0, 0

	// Occupied space
	var out bytes.Buffer
	var errOut bytes.Buffer
	command := exec.Command("/usr/bin/du", "-b", "-s", path)
	command.Stdout = &out
	command.Stderr = &errOut
	err := command.Run()
	if err != nil {
		log.Println(errOut.String())
		return space, inodes, err
	}
	fields := strings.Fields(strings.TrimSpace(out.String()))
	out.Reset()
	errOut.Reset()

	if len(fields) == 2 {
		space, err = strconv.Atoi(fields[0])
		if err != nil {
			return space, inodes, err
		}
	}

	// Occupied inodes
	command = exec.Command("/usr/bin/du", "--inodes", "-s", path)
	command.Stdout = &out
	command.Stderr = &errOut
	err = command.Run()
	if err != nil {
		log.Println(errOut.String())
		return space, inodes, err
	}
	fields = strings.Fields(strings.TrimSpace(out.String()))
	out.Reset()
	errOut.Reset()

	if len(fields) == 2 {
		inodes, err = strconv.Atoi(fields[0])
		if err != nil {
			return inodes, inodes, err
		}
	}

	return space, inodes, nil
}

// Removes content of given directory and then the directory itself
func removeDirectory(dir string) error {
	d, err := os.Open(dir)
	if err != nil {
		return err
	}
	defer d.Close()
	names, err := d.Readdirnames(-1)
	if err != nil {
		return err
	}
	for _, name := range names {
		err = os.RemoveAll(filepath.Join(dir, name))
		if err != nil {
			return err
		}
	}

	err = os.Remove(filepath.Join(dir))
	if err != nil {
		return err
	}

	return nil
}

// CPUMemoryStats returns list of applications with updated CPU and memory usage effectively.
// Sample is number of seconds between two measurements of the CPU usage. More means more precise.
func CPUMemoryStats(applist *[]apps.App, sample int) (*[]apps.App, error) {
	if sample <= 0 {
		sample = 1
	}

	containers := []Container{}
	for _, app := range *applist {
		containers = append(containers, Container{App: &app})
	}

	startMap := make(map[string]int64)
	endMap := make(map[string]int64)

	start := time.Now().UnixNano()
	for _, container := range containers {
		cpu, _, err := container.GetRawResourceStats()
		if err != nil {
			return nil, err
		}
		startMap[container.App.Name] = cpu
	}

	time.Sleep(time.Duration(sample) * time.Second)

	for idx, container := range containers {
		cpu, memory, err := container.GetRawResourceStats()
		if err != nil {
			return nil, err
		}
		endMap[container.App.Name] = cpu
		containers[idx].App.MemoryUsage = memory
	}

	end := time.Now().UnixNano()
	difference := (float64(end) - float64(start)) / float64(1000000000)

	updatedApps := []apps.App{}
	for _, container := range containers {
		app := *container.App
		app.CPUUsage = (float64(endMap[app.Name]) - float64(startMap[app.Name])) / difference / 10000000.0
		updatedApps = append(updatedApps, app)
	}

	return &updatedApps, nil
}

func getTechAndVersion(symlink string) (*TechInfo, error) {
	link, err := os.Readlink(symlink)
	if os.IsNotExist(err) {
		return &TechInfo{
			Tech:    "default",
			Version: "",
		}, nil
	}
	if err != nil {
		return nil, fmt.Errorf("error reading symlink: %w", err)
	}

	absLink, err := filepath.Abs(link)
	if err != nil {
		return nil, fmt.Errorf("error getting absolute path: %w", err)
	}
	absLink = strings.TrimSuffix(absLink, "/bin")

	dirName := filepath.Base(absLink)
	parts := strings.Split(dirName, "-")
	fmt.Println("DEBUG", symlink)
	fmt.Println("DEBUG", absLink)
	fmt.Println("DEBUG", dirName)
	fmt.Println("DEBUG", parts)
	if len(parts) < 2 {
		return &TechInfo{
			Tech:    "default",
			Version: "",
		}, nil
	}

	re := regexp.MustCompile(`\d+\.\d+\.\d+`)
	version := re.FindString(parts[1])
	if version == "" {
		// In case version couldn't be determined we return "unknown", otherwise returning
		// error in this case was crashing admin when user fucked up the symlink for the
		// tech manually.
		log.Println("failed to extract version from symlink")
		return &TechInfo{
			Tech:    "unknown",
			Version: "",
		}, nil
	}

	return &TechInfo{
		Tech:    parts[0],
		Version: version,
	}, nil
}