package platform

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strconv"

	"github.com/Eriyc/rules_wails/pkg/wails3kit/updates"
)

type restorePoint struct {
	path     string
	backup   string
	hadPrior bool
}

func applyBundle(request helperRequest) error {
	backupDir, err := os.MkdirTemp("", "wails3kit-backup-*")
	if err != nil {
		return err
	}

	restores := make([]restorePoint, 0, len(request.Bundle.Files))
	for _, file := range request.Bundle.Files {
		source := filepath.Join(request.StagedPath, filepath.FromSlash(file.Path))
		target := filepath.Join(request.InstallRoot, filepath.FromSlash(file.Path))
		modeValue, err := strconv.ParseUint(file.Mode, 8, 32)
		if err != nil {
			rollback(restores)
			return err
		}
		mode := os.FileMode(modeValue)

		restore, err := backupTarget(backupDir, target)
		if err != nil {
			rollback(restores)
			return err
		}
		restores = append(restores, restore)

		if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
			rollback(restores)
			return err
		}
		if err := os.RemoveAll(target); err != nil {
			rollback(restores)
			return err
		}
		if err := copyFile(source, target, mode); err != nil {
			rollback(restores)
			return err
		}
	}

	return nil
}

func backupTarget(backupDir string, target string) (restorePoint, error) {
	point := restorePoint{path: target}
	info, err := os.Stat(target)
	if err != nil {
		if os.IsNotExist(err) {
			return point, nil
		}
		return point, err
	}
	if info.IsDir() {
		return point, fmt.Errorf("%w: target %s is a directory", updates.ErrInvalidArtifact, target)
	}

	backupPath := filepath.Join(backupDir, filepath.Base(target))
	if err := copyFile(target, backupPath, info.Mode()); err != nil {
		return point, err
	}
	point.hadPrior = true
	point.backup = backupPath
	return point, nil
}

func rollback(restores []restorePoint) {
	for index := len(restores) - 1; index >= 0; index-- {
		restore := restores[index]
		if restore.hadPrior {
			info, err := os.Stat(restore.backup)
			if err == nil {
				_ = os.RemoveAll(restore.path)
				_ = os.MkdirAll(filepath.Dir(restore.path), 0o755)
				_ = copyFile(restore.backup, restore.path, info.Mode())
			}
			continue
		}
		_ = os.RemoveAll(restore.path)
	}
}

func copyFile(sourcePath string, destinationPath string, mode os.FileMode) error {
	source, err := os.Open(sourcePath)
	if err != nil {
		return err
	}
	defer source.Close()

	return writeFile(destinationPath, source, mode)
}

func writeFile(path string, source io.Reader, mode os.FileMode) error {
	file, err := os.Create(path)
	if err != nil {
		return err
	}
	defer file.Close()

	if _, err := io.Copy(file, source); err != nil {
		return err
	}
	return file.Chmod(mode)
}
