package application

import (
	"NanoKVM-Server/proto"
	"NanoKVM-Server/utils"
	"os"
	"os/exec"
	"path/filepath"
	"time"

	"github.com/gin-gonic/gin"
	log "github.com/sirupsen/logrus"
)

func (s *Service) UpdateServer(c *gin.Context) {
	var rsp proto.Response

	file, err := c.FormFile("file")
	if err != nil {
		rsp.ErrRsp(c, -1, "invalid file")
		return
	}

	// Get executable path
	execPath, err := os.Executable()
	if err != nil {
		rsp.ErrRsp(c, -2, "get executable path failed")
		return
	}
	execDir := filepath.Dir(execPath)

	// Save to temp file in SAME directory to ensure atomic rename
	tmpPath := filepath.Join(execDir, "NanoKVM-Server.new")
	if err := c.SaveUploadedFile(file, tmpPath); err != nil {
		rsp.ErrRsp(c, -3, "save file failed")
		return
	}

	// Verify file size
	info, err := os.Stat(tmpPath)
	if err != nil || info.Size() == 0 {
		_ = os.Remove(tmpPath)
		rsp.ErrRsp(c, -4, "invalid file size")
		return
	}

	// Verify it's executable
	if err := os.Chmod(tmpPath, 0755); err != nil {
		_ = os.Remove(tmpPath)
		rsp.ErrRsp(c, -5, "chmod failed")
		return
	}

	// Backup current binary
	backupPath := execPath + ".bak"
	// Try to remove old backup first
	_ = os.Remove(backupPath)
	
	if err := os.Rename(execPath, backupPath); err != nil {
		// If rename fails (e.g. running binary locked?), try copy
		if err := utils.CopyFile(execPath, backupPath); err != nil {
			log.Warnf("backup failed: %v", err)
			// Proceed with caution or fail? 
			// If we can't backup, maybe we shouldn't proceed.
			// But for embedded systems, sometimes we just overwrite.
		}
	}

	// Replace binary
	if err := os.Rename(tmpPath, execPath); err != nil {
		// Attempt rollback
		_ = os.Rename(backupPath, execPath)
		rsp.ErrRsp(c, -6, "replace binary failed")
		return
	}

	rsp.OkRsp(c)
	
	// Restart service in background
	go func() {
		time.Sleep(1 * time.Second)
		_ = exec.Command("sh", "-c", "/etc/init.d/S95nanokvm restart").Run()
	}()
}

func (s *Service) UpdateWeb(c *gin.Context) {
	var rsp proto.Response

	file, err := c.FormFile("file")
	if err != nil {
		rsp.ErrRsp(c, -1, "invalid file")
		return
	}

	// Get executable path
	execPath, err := os.Executable()
	if err != nil {
		rsp.ErrRsp(c, -2, "get executable path failed")
		return
	}
	execDir := filepath.Dir(execPath)

	// Save to temp file in SAME directory
	tmpPath := filepath.Join(execDir, "web.tar.gz.new")
	if err := c.SaveUploadedFile(file, tmpPath); err != nil {
		rsp.ErrRsp(c, -3, "save file failed")
		return
	}

	// Verify file size
	info, err := os.Stat(tmpPath)
	if err != nil || info.Size() == 0 {
		_ = os.Remove(tmpPath)
		rsp.ErrRsp(c, -4, "invalid file size")
		return
	}

	// Define web directory
	webDir := filepath.Join(execDir, "web")
	
	// Extract to temp dir first
	extractDir := filepath.Join(execDir, "web_extract_tmp")
	os.RemoveAll(extractDir)
	os.MkdirAll(extractDir, 0755)

	if _, err := utils.UnTarGz(tmpPath, extractDir); err != nil {
		os.RemoveAll(extractDir)
		_ = os.Remove(tmpPath)
		rsp.ErrRsp(c, -5, "extract failed")
		return
	}

	// Check if there is a single directory inside extractDir (e.g. 'dist')
	entries, _ := os.ReadDir(extractDir)
	sourceDir := extractDir
	if len(entries) == 1 && entries[0].IsDir() {
		sourceDir = filepath.Join(extractDir, entries[0].Name())
	}

	// Backup old web dir
	backupWebDir := filepath.Join(execDir, "web.bak")
	os.RemoveAll(backupWebDir)
	_ = os.Rename(webDir, backupWebDir)

	// Move new web dir
	if err := os.Rename(sourceDir, webDir); err != nil {
		// Rollback
		_ = os.Rename(backupWebDir, webDir)
		os.RemoveAll(extractDir)
		_ = os.Remove(tmpPath)
		rsp.ErrRsp(c, -6, "replace web dir failed")
		return
	}

	// Cleanup
	os.RemoveAll(extractDir)
	os.RemoveAll(backupWebDir)
	_ = os.Remove(tmpPath)

	rsp.OkRsp(c)
}
