package appstate

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"strings"
)

const (
	appDirName  = "QuoteForge"
	defaultDB   = "qfs.db"
	defaultCfg  = "config.yaml"
	envDBPath   = "QFS_DB_PATH"
	envStateDir = "QFS_STATE_DIR"
	envCfgPath  = "QFS_CONFIG_PATH"
)

// ResolveDBPath returns the local SQLite path using priority:
// explicit CLI path > QFS_DB_PATH > OS-specific user state directory.
func ResolveDBPath(explicitPath string) (string, error) {
	if explicitPath != "" {
		return filepath.Clean(explicitPath), nil
	}

	if fromEnv := os.Getenv(envDBPath); fromEnv != "" {
		return filepath.Clean(fromEnv), nil
	}

	dir, err := defaultStateDir()
	if err != nil {
		return "", err
	}

	return filepath.Join(dir, defaultDB), nil
}

// ResolveConfigPath returns the config path using priority:
// explicit CLI path > QFS_CONFIG_PATH > OS-specific user state directory.
func ResolveConfigPath(explicitPath string) (string, error) {
	if explicitPath != "" {
		return filepath.Clean(explicitPath), nil
	}

	if fromEnv := os.Getenv(envCfgPath); fromEnv != "" {
		return filepath.Clean(fromEnv), nil
	}

	dir, err := defaultStateDir()
	if err != nil {
		return "", err
	}

	return filepath.Join(dir, defaultCfg), nil
}

// ResolveConfigPathNearDB returns config path using priority:
// explicit CLI path > QFS_CONFIG_PATH > directory of resolved local DB path.
// Falls back to ResolveConfigPath when dbPath is empty.
func ResolveConfigPathNearDB(explicitPath, dbPath string) (string, error) {
	if explicitPath != "" {
		return filepath.Clean(explicitPath), nil
	}

	if fromEnv := os.Getenv(envCfgPath); fromEnv != "" {
		return filepath.Clean(fromEnv), nil
	}

	if strings.TrimSpace(dbPath) != "" {
		return filepath.Join(filepath.Dir(filepath.Clean(dbPath)), defaultCfg), nil
	}

	return ResolveConfigPath("")
}

// MigrateLegacyDB copies an existing legacy DB (and optional SQLite sidecars)
// to targetPath if targetPath does not already exist.
// Returns source path if migration happened.
func MigrateLegacyDB(targetPath string, legacyPaths []string) (string, error) {
	if targetPath == "" {
		return "", nil
	}

	if exists(targetPath) {
		return "", nil
	}

	if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
		return "", fmt.Errorf("creating target db directory: %w", err)
	}

	for _, src := range legacyPaths {
		if src == "" {
			continue
		}
		src = filepath.Clean(src)
		if src == targetPath || !exists(src) {
			continue
		}

		if err := copyFile(src, targetPath); err != nil {
			return "", fmt.Errorf("migrating legacy db from %s: %w", src, err)
		}

		// Optional SQLite sidecar files.
		_ = copyIfExists(src+"-wal", targetPath+"-wal")
		_ = copyIfExists(src+"-shm", targetPath+"-shm")

		return src, nil
	}

	return "", nil
}

// MigrateLegacyFile copies an existing legacy file to targetPath
// if targetPath does not already exist.
func MigrateLegacyFile(targetPath string, legacyPaths []string) (string, error) {
	if targetPath == "" {
		return "", nil
	}

	if exists(targetPath) {
		return "", nil
	}

	if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
		return "", fmt.Errorf("creating target directory: %w", err)
	}

	for _, src := range legacyPaths {
		if src == "" {
			continue
		}
		src = filepath.Clean(src)
		if src == targetPath || !exists(src) {
			continue
		}
		if err := copyFile(src, targetPath); err != nil {
			return "", fmt.Errorf("migrating legacy file from %s: %w", src, err)
		}
		return src, nil
	}

	return "", nil
}

func defaultStateDir() (string, error) {
	if override := os.Getenv(envStateDir); override != "" {
		return filepath.Clean(override), nil
	}

	switch runtime.GOOS {
	case "darwin":
		base, err := os.UserConfigDir() // ~/Library/Application Support
		if err != nil {
			return "", fmt.Errorf("resolving user config dir: %w", err)
		}
		return filepath.Join(base, appDirName), nil
	case "windows":
		if local := os.Getenv("LOCALAPPDATA"); local != "" {
			return filepath.Join(local, appDirName), nil
		}
		base, err := os.UserConfigDir()
		if err != nil {
			return "", fmt.Errorf("resolving user config dir: %w", err)
		}
		return filepath.Join(base, appDirName), nil
	default:
		if xdgState := os.Getenv("XDG_STATE_HOME"); xdgState != "" {
			return filepath.Join(xdgState, "quoteforge"), nil
		}
		home, err := os.UserHomeDir()
		if err != nil {
			return "", fmt.Errorf("resolving user home dir: %w", err)
		}
		return filepath.Join(home, ".local", "state", "quoteforge"), nil
	}
}

func exists(path string) bool {
	_, err := os.Stat(path)
	return err == nil
}

func copyIfExists(src, dst string) error {
	if !exists(src) {
		return nil
	}
	return copyFile(src, dst)
}

func copyFile(src, dst string) error {
	in, err := os.Open(src)
	if err != nil {
		return err
	}
	defer in.Close()

	info, err := in.Stat()
	if err != nil {
		return err
	}

	out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, info.Mode().Perm())
	if err != nil {
		return err
	}
	defer out.Close()

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

	return out.Sync()
}
