package tools

import (
	"context"
	"encoding/json"
	"fmt"
	"log/slog"
	"os"
	"path/filepath"
	"strings"
	"time"

	go_pkg_filesystem "github.com/pardnchiu/go-pkg/filesystem"
	go_pkg_filesystem_reader "github.com/pardnchiu/go-pkg/filesystem/reader"

	"github.com/pardnchiu/agenvoy/configs"
	toolTypes "github.com/pardnchiu/agenvoy/internal/tools/types"
)

type deniedConfig struct {
	Dirs       []string `json:"dirs"`
	Files      []string `json:"files"`
	Prefixes   []string `json:"prefixes"`
	Extensions []string `json:"extensions"`
}

var DeniedConfig = func() deniedConfig {
	var cfg deniedConfig
	if err := json.Unmarshal(configs.DeniedMap, &cfg); err != nil {
		slog.Warn("json.Unmarshal",
			slog.String("error", err.Error()))
	}
	return cfg
}()

var WorkDirChangeHook func(path string)

func changeWorkDir(e *toolTypes.Executor, args []string) (string, error) {
	var positional []string
	for _, a := range args {
		if !strings.HasPrefix(a, "-") {
			positional = append(positional, a)
		}
	}
	if len(positional) > 1 {
		return "", fmt.Errorf("cd accepts at most one positional argument, got %d", len(positional))
	}

	var target string
	switch {
	case len(positional) == 0 || positional[0] == "~":
		home, err := os.UserHomeDir()
		if err != nil {
			return "", fmt.Errorf("os.UserHomeDir: %w", err)
		}
		target = home
	case strings.HasPrefix(positional[0], "~/"):
		home, err := os.UserHomeDir()
		if err != nil {
			return "", fmt.Errorf("os.UserHomeDir: %w", err)
		}
		target = filepath.Join(home, positional[0][2:])
	default:
		target = positional[0]
	}

	if !filepath.IsAbs(target) {
		target = filepath.Join(e.WorkDir, target)
	}
	abs, err := filepath.Abs(target)
	if err != nil {
		return "", fmt.Errorf("filepath.Abs: %w", err)
	}
	abs = filepath.Clean(abs)

	for _, dir := range DeniedConfig.Dirs {
		needle := "/" + dir
		if strings.Contains(abs, needle+"/") || strings.HasSuffix(abs, needle) {
			return "", fmt.Errorf("access denied: %s", dir)
		}
	}

	if !go_pkg_filesystem_reader.IsDir(abs) {
		return "", fmt.Errorf("not a directory or does not exist: %s", abs)
	}

	e.WorkDir = abs
	if WorkDirChangeHook != nil {
		WorkDirChangeHook(abs)
	}
	return fmt.Sprintf("Changed working directory to: %s", abs), nil
}

func moveToTrash(ctx context.Context, e *toolTypes.Executor, args []string) (string, error) {
	trashPath := filepath.Join(e.WorkDir, ".Trash")
	if err := go_pkg_filesystem.CheckDir(trashPath, true); err != nil {
		return "", fmt.Errorf("go_pkg_filesystem.CheckDir .Trash: %w", err)
	}

	var moved []string
	for _, arg := range args {
		if err := ctx.Err(); err != nil {
			return "", fmt.Errorf("moveToTrash cancelled: %w", err)
		}
		if strings.HasPrefix(arg, "-") {
			continue
		}
		src := filepath.Join(e.WorkDir, filepath.Clean(arg))
		name := filepath.Base(arg)
		dst := filepath.Join(trashPath, name)

		if go_pkg_filesystem_reader.Exists(dst) {
			ext := filepath.Ext(name)
			dst = filepath.Join(trashPath, fmt.Sprintf("%s_%s%s",
				strings.TrimSuffix(name, ext),
				time.Now().Format("20060102_150405"),
				ext))
		}

		if err := os.Rename(src, dst); err == nil {
			moved = append(moved, arg)
		}
	}
	return fmt.Sprintf("Successfully moved to .Trash: %s", strings.Join(moved, ", ")), nil
}
