package backend_golang

import (
	"encoding/json"
	"errors"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	wruntime "github.com/wailsapp/wails/v2/pkg/runtime"
)

func (a *App) GetAbsPath(path string) (string, error) {
	var absPath string
	var err error
	if filepath.IsAbs(path) {
		absPath = filepath.Clean(path)
	} else {
		absPath, err = filepath.Abs(filepath.Join(a.exDir, path))
		if err != nil {
			return "", err
		}
	}
	absPath = strings.ReplaceAll(absPath, "/", string(os.PathSeparator))
	println("GetAbsPath:", absPath)
	return absPath, nil
}

func (a *App) SaveFile(path string, savedContent []byte) error {
	absPath, err := a.GetAbsPath(path)
	if err != nil {
		return err
	}
	if err := os.WriteFile(absPath, savedContent, 0644); err != nil {
		return err
	}
	return nil
}

func (a *App) SaveJson(path string, jsonData any) error {
	text, err := json.MarshalIndent(jsonData, "", "  ")
	if err != nil {
		return err
	}

	absPath, err := a.GetAbsPath(path)
	if err != nil {
		return err
	}
	if err := os.WriteFile(absPath, text, 0644); err != nil {
		return err
	}
	return nil
}

func (a *App) ReadJson(path string) (any, error) {
	absPath, err := a.GetAbsPath(path)
	if err != nil {
		return nil, err
	}
	file, err := os.ReadFile(absPath)
	if err != nil {
		return nil, err
	}

	var data any
	err = json.Unmarshal(file, &data)
	if err != nil {
		return nil, err
	}

	return data, nil
}

func (a *App) FileExists(path string) bool {
	absPath, err := a.GetAbsPath(path)
	if err != nil {
		return false
	}
	_, err = os.Stat(absPath)
	return err == nil
}

type FileInfo struct {
	Name    string `json:"name"`
	Size    int64  `json:"size"`
	IsDir   bool   `json:"isDir"`
	ModTime string `json:"modTime"`
}

func (a *App) ReadFileInfo(path string) (*FileInfo, error) {
	absPath, err := a.GetAbsPath(path)
	if err != nil {
		return nil, err
	}
	info, err := os.Stat(absPath)
	if err != nil {
		return nil, err
	}
	return &FileInfo{
		Name:    info.Name(),
		Size:    info.Size(),
		IsDir:   info.IsDir(),
		ModTime: info.ModTime().Format(time.RFC3339),
	}, nil
}

func (a *App) ListDirFiles(dirPath string) ([]FileInfo, error) {
	absDirPath, err := a.GetAbsPath(dirPath)
	if err != nil {
		return nil, err
	}
	files, err := os.ReadDir(absDirPath)
	if err != nil {
		return nil, err
	}

	var filesInfo []FileInfo
	for _, file := range files {
		info, err := file.Info()
		if err != nil {
			return nil, err
		}
		filesInfo = append(filesInfo, FileInfo{
			Name:    info.Name(),
			Size:    info.Size(),
			IsDir:   info.IsDir(),
			ModTime: info.ModTime().Format(time.RFC3339),
		})
	}
	return filesInfo, nil
}

func (a *App) DeleteFile(path string) error {
	absPath, err := a.GetAbsPath(path)
	if err != nil {
		return err
	}
	err = os.Remove(absPath)
	if err != nil {
		return err
	}
	return nil
}

func (a *App) CopyFile(src string, dst string) error {
	absSrc, err := a.GetAbsPath(src)
	if err != nil {
		return err
	}
	absDst, err := a.GetAbsPath(dst)
	if err != nil {
		return err
	}

	sourceFile, err := os.Open(absSrc)
	if err != nil {
		return err
	}
	defer sourceFile.Close()

	err = os.MkdirAll(filepath.Dir(absDst), 0755)
	if err != nil {
		return err
	}

	destFile, err := os.Create(absDst)
	if err != nil {
		return err
	}
	defer destFile.Close()

	_, err = io.Copy(destFile, sourceFile)
	if err != nil {
		return err
	}
	return nil
}

func (a *App) OpenSaveFileDialog(filterPattern string, defaultFileName string, savedContent string) (string, error) {
	return a.OpenSaveFileDialogBytes(filterPattern, defaultFileName, []byte(savedContent))
}

func (a *App) OpenSaveFileDialogBytes(filterPattern string, defaultFileName string, savedContent []byte) (string, error) {
	path, err := wruntime.SaveFileDialog(a.ctx, wruntime.SaveDialogOptions{
		DefaultFilename: defaultFileName,
		Filters: []wruntime.FileFilter{{
			Pattern: filterPattern,
		}},
		CanCreateDirectories: true,
	})
	if err != nil {
		return "", err
	}
	if path == "" {
		return "", nil
	}
	if err := os.WriteFile(path, savedContent, 0644); err != nil {
		return "", err
	}
	return path, nil
}

// Only return the path of the selected file, because communication between frontend and backend is slow. Use AssetServer Handler to read the file.
func (a *App) OpenOpenFileDialog(filterPattern string) (string, error) {
	path, err := wruntime.OpenFileDialog(a.ctx, wruntime.OpenDialogOptions{
		Filters: []wruntime.FileFilter{{Pattern: filterPattern}},
	})
	if err != nil {
		return "", err
	}
	if path == "" {
		return "", nil
	}
	return path, nil
}

func (a *App) OpenFileFolder(path string) error {
	absPath, err := a.GetAbsPath(path)
	if err != nil {
		return err
	}
	switch os := runtime.GOOS; os {
	case "windows":
		cmd := exec.Command("explorer", "/select,", absPath)
		err := cmd.Run()
		if err != nil {
			return err
		}
		return nil
	case "darwin":
		cmd := exec.Command("open", "-R", absPath)
		err := cmd.Run()
		if err != nil {
			return err
		}
		return nil
	case "linux":
		cmd := exec.Command("xdg-open", absPath)
		err := cmd.Run()
		if err != nil {
			return err
		}
		return nil
	}
	return errors.New("unsupported OS")
}

func (a *App) StartFile(path string) error {
	cmd, err := CmdHelper(true, path)
	if err != nil {
		return err
	}
	err = cmd.Start()
	return err
}
