package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path/filepath"

	"kisekinopureya.com.tr/updater/internal/archive"
	"kisekinopureya.com.tr/updater/internal/logger"
	mountfilesystem "kisekinopureya.com.tr/updater/internal/mountFilesystem"
	"kisekinopureya.com.tr/updater/internal/plymouth"
)

type BlockDevice struct {
	Name     string        `json:"name"`
	Type     string        `json:"type"`
	PartType string        `json:"parttype"`
	Children []BlockDevice `json:"children,omitempty"`
}

type LSBLK struct {
	BlockDevices []BlockDevice `json:"blockdevices"`
}

type partitionScheme struct {
	EfiDevice      string
	RootfsDevice   string
	DmVerityDevice string
	//VarDevice string
}

var efiUUID string = "c12a7328-f81f-11d2-ba4b-00a0c93ec93b"
var rootfsUUID string = "4f68bce3-e8cd-4db1-96e7-fbcaf984b709"
var dmverityUUID string = "2c7357ed-ebd2-46d9-aec1-23d437ec2bf5"

//var varUUID string = "4d21b016-b534-45c2-a9fb-5c16e091fd2d"

func findByPartType(devices []BlockDevice, target string) string {
	for _, dev := range devices {
		if dev.PartType != "" && dev.PartType == target {
			return dev.Name
		}
		if len(dev.Children) > 0 {
			if found := findByPartType(dev.Children, target); found != "" {
				return found
			}
		}
	}
	return ""
}

func populatePartitionScheme() partitionScheme {

	var generatedScheme partitionScheme = partitionScheme{}
	cmd := exec.Command("lsblk", "-J", "-o", "NAME,TYPE,PARTTYPE")
	blockDeviceTypes, err := cmd.Output()

	if err != nil {
		log.Panic(err)
	}

	var lsblk LSBLK
	if err := json.Unmarshal(blockDeviceTypes, &lsblk); err != nil {
		log.Panic(err)
	}
	generatedScheme.EfiDevice = findByPartType(lsblk.BlockDevices, efiUUID)
	generatedScheme.RootfsDevice = findByPartType(lsblk.BlockDevices, rootfsUUID)
	generatedScheme.DmVerityDevice = findByPartType(lsblk.BlockDevices, dmverityUUID)

	return generatedScheme
}

func patchImage(imageFile string, partition string, name string) {
	cmd := exec.Command("dd", "if="+imageFile, "of="+"/dev/"+partition, "bs=4M")
	plymouth.ShowPlymouthMessage("Patching " + name + " partition")
	if err := cmd.Run(); err != nil {
		log.Panic(err)
	}
}

func CopyFile(source, destPath string) error {
	in, err := os.Open(source)
	if err != nil {
		log.Panic(err)
	}
	defer in.Close()

	if err := os.MkdirAll(filepath.Dir(destPath), 0755); err != nil {
		log.Panic(err)
	}

	out, err := os.Create(destPath)
	if err != nil {
		log.Panic(err)
	}
	defer out.Close()

	if _, err := io.Copy(out, in); err != nil {
		log.Panic(err)
	}

	if err := out.Sync(); err != nil {
		log.Panic(err)
	}

	return nil
}

func main() {
	logger.InitializeLogger("/mnt/var/log/patcher")

	if len(os.Args) != 2 {
		fmt.Printf("Usage: %s <metadata>\n", os.Args[0])
		os.Exit(1)
	}

	f, err := os.Open(os.Args[1])
	if err != nil {
		panic(err)
	}
	defer f.Close()

	var meta archive.Output
	if err := json.NewDecoder(f).Decode(&meta); err != nil {
		panic(err)
	}

	currentPartitionScheme := populatePartitionScheme()

	fileCount := len(meta.Files)

	// before 20 and after 80 will be filled by initramfs
	leftProgress := 60/fileCount + 20

	for _, f := range meta.Files {
		plymouth.ShowPlymouthProgress(leftProgress)
		leftProgress += leftProgress

		imageFile := f.Name
		imageType := f.Type

		switch imageType {
		case "rootfs":
			patchImage(imageFile, currentPartitionScheme.RootfsDevice, imageType)
		case "dm-verity":
			patchImage(imageFile, currentPartitionScheme.DmVerityDevice, imageType)
		case "kernel":
			mountPath := "/updates-tmp/efi"
			kernelDir := "/EFI/Linux"
			mountfilesystem.MountFileSystem("/dev/"+currentPartitionScheme.EfiDevice, mountPath, "vfat", 0, "")
			CopyFile(imageFile, mountPath+kernelDir+"/kernel.efi")
			mountfilesystem.UnmountFileSystem(mountPath)
		default:
		}
	}
}
