package upgrader

import (
	"io"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"time"

	"code.pfad.fr/risefront"
	"github.com/minio/selfupdate"
	"github.com/pkg/errors"
)

func (u *Upgrader) TestCommitAndRestart() error {
	// Get the directory of the current executable
	exDir := filepath.Dir(u.ExPath)
	testBinaryPath := filepath.Join(exDir, "nginx-ui.test")

	// Create temporary old file path
	oldExe := ""
	if runtime.GOOS == "windows" {
		oldExe = filepath.Join(exDir, ".nginx-ui.old."+strconv.FormatInt(time.Now().Unix(), 10))
	}

	// Setup update options
	opts := selfupdate.Options{
		OldSavePath: oldExe,
	}

	// Check permissions
	if err := opts.CheckPermissions(); err != nil {
		return err
	}

	// Copy current executable to test file
	srcFile, err := os.Open(u.ExPath)
	if err != nil {
		return errors.Wrap(err, "failed to open source executable")
	}
	defer srcFile.Close()

	// Create destination file
	destFile, err := os.Create(testBinaryPath)
	if err != nil {
		return errors.Wrap(err, "failed to create test executable")
	}
	defer destFile.Close()

	// Copy file content
	_, err = io.Copy(destFile, srcFile)
	if err != nil {
		return errors.Wrap(err, "failed to copy executable content")
	}

	// Set executable permissions
	if err = destFile.Chmod(0755); err != nil {
		return errors.Wrap(err, "failed to set executable permission")
	}

	// Reopen file for selfupdate
	srcFile.Close()
	srcFile, err = os.Open(testBinaryPath)
	if err != nil {
		return errors.Wrap(err, "failed to open test executable for update")
	}
	defer srcFile.Close()

	// Prepare and check binary
	if err = selfupdate.PrepareAndCheckBinary(srcFile, opts); err != nil {
		var pathErr *os.PathError
		if errors.As(err, &pathErr) {
			return pathErr.Err
		}
		return err
	}

	// Commit binary update
	if err = selfupdate.CommitBinary(opts); err != nil {
		if rerr := selfupdate.RollbackError(err); rerr != nil {
			return rerr
		}
		var pathErr *os.PathError
		if errors.As(err, &pathErr) {
			return pathErr.Err
		}
		return err
	}

	_ = os.Remove(testBinaryPath)

	// Wait for file to be written
	time.Sleep(1 * time.Second)

	// Gracefully restart
	risefront.Restart()
	return nil
}
