package configops

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"

	"github.com/YspCoder/clawgo/pkg/config"
)

func LoadConfigAsMap(path string) (map[string]interface{}, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		if os.IsNotExist(err) {
			defaultCfg := config.DefaultConfig()
			defData, mErr := json.Marshal(defaultCfg)
			if mErr != nil {
				return nil, mErr
			}
			var cfgMap map[string]interface{}
			if uErr := json.Unmarshal(defData, &cfgMap); uErr != nil {
				return nil, uErr
			}
			return cfgMap, nil
		}
		return nil, err
	}

	var cfgMap map[string]interface{}
	if err := json.Unmarshal(data, &cfgMap); err != nil {
		return nil, err
	}
	return cfgMap, nil
}

func NormalizeConfigPath(path string) string {
	p := strings.TrimSpace(path)
	p = strings.Trim(p, ".")
	if p == "" {
		return ""
	}
	parts := strings.Split(p, ".")
	out := make([]string, 0, len(parts))
	for _, part := range parts {
		part = strings.TrimSpace(part)
		if part == "" {
			continue
		}
		if part == "enable" {
			part = "enabled"
		}
		out = append(out, part)
	}
	return strings.Join(out, ".")
}

func ParseConfigValue(raw string) interface{} {
	v := strings.TrimSpace(raw)
	lv := strings.ToLower(v)
	if lv == "true" {
		return true
	}
	if lv == "false" {
		return false
	}
	if lv == "null" {
		return nil
	}
	if i, err := strconv.ParseInt(v, 10, 64); err == nil {
		return i
	}
	if f, err := strconv.ParseFloat(v, 64); err == nil && strings.Contains(v, ".") {
		return f
	}
	if len(v) >= 2 && ((v[0] == '"' && v[len(v)-1] == '"') || (v[0] == '\'' && v[len(v)-1] == '\'')) {
		return v[1 : len(v)-1]
	}
	if strings.HasPrefix(v, "{") || strings.HasPrefix(v, "[") {
		var parsed interface{}
		if err := json.Unmarshal([]byte(v), &parsed); err == nil {
			return parsed
		}
	}
	return v
}

func SetMapValueByPath(root map[string]interface{}, path string, value interface{}) error {
	if root == nil {
		return fmt.Errorf("root is nil")
	}
	path = NormalizeConfigPath(path)
	if path == "" {
		return fmt.Errorf("path is empty")
	}
	parts := strings.Split(path, ".")
	cur := root
	for i := 0; i < len(parts)-1; i++ {
		key := parts[i]
		if key == "" {
			return fmt.Errorf("invalid path: %s", path)
		}
		next, ok := cur[key]
		if !ok {
			child := map[string]interface{}{}
			cur[key] = child
			cur = child
			continue
		}
		child, ok := next.(map[string]interface{})
		if !ok {
			return fmt.Errorf("path segment is not object: %s", key)
		}
		cur = child
	}
	last := parts[len(parts)-1]
	if last == "" {
		return fmt.Errorf("invalid path: %s", path)
	}
	cur[last] = value
	return nil
}

func GetMapValueByPath(root map[string]interface{}, path string) (interface{}, bool) {
	if root == nil {
		return nil, false
	}
	path = NormalizeConfigPath(path)
	if path == "" {
		return nil, false
	}
	parts := strings.Split(path, ".")
	var cur interface{} = root
	for _, key := range parts {
		obj, ok := cur.(map[string]interface{})
		if !ok {
			return nil, false
		}
		next, ok := obj[key]
		if !ok {
			return nil, false
		}
		cur = next
	}
	return cur, true
}

func WriteConfigAtomicWithBackup(configPath string, data []byte) (string, error) {
	if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil {
		return "", err
	}

	backupPath := configPath + ".bak"
	if oldData, err := os.ReadFile(configPath); err == nil {
		if err := os.WriteFile(backupPath, oldData, 0644); err != nil {
			return "", fmt.Errorf("write backup failed: %w", err)
		}
	} else if !os.IsNotExist(err) {
		return "", fmt.Errorf("read existing config failed: %w", err)
	}

	tmpPath := configPath + ".tmp"
	if err := os.WriteFile(tmpPath, data, 0644); err != nil {
		return "", fmt.Errorf("write temp config failed: %w", err)
	}
	if err := os.Rename(tmpPath, configPath); err != nil {
		_ = os.Remove(tmpPath)
		return "", fmt.Errorf("atomic replace config failed: %w", err)
	}
	return backupPath, nil
}

func RollbackConfigFromBackup(configPath, backupPath string) error {
	backupData, err := os.ReadFile(backupPath)
	if err != nil {
		return fmt.Errorf("read backup failed: %w", err)
	}
	tmpPath := configPath + ".rollback.tmp"
	if err := os.WriteFile(tmpPath, backupData, 0644); err != nil {
		return fmt.Errorf("write rollback temp failed: %w", err)
	}
	if err := os.Rename(tmpPath, configPath); err != nil {
		_ = os.Remove(tmpPath)
		return fmt.Errorf("rollback replace failed: %w", err)
	}
	return nil
}

func TriggerGatewayReload(configPath string, notRunningErr error) (bool, error) {
	pidPath := filepath.Join(filepath.Dir(configPath), "gateway.pid")
	data, err := os.ReadFile(pidPath)
	if err != nil {
		return false, fmt.Errorf("%w (pid file not found: %s)", notRunningErr, pidPath)
	}

	pidStr := strings.TrimSpace(string(data))
	pid, err := strconv.Atoi(pidStr)
	if err != nil || pid <= 0 {
		return true, fmt.Errorf("invalid gateway pid: %q", pidStr)
	}

	proc, err := os.FindProcess(pid)
	if err != nil {
		return true, fmt.Errorf("find process failed: %w", err)
	}
	if err := proc.Signal(syscall.SIGHUP); err != nil {
		return true, fmt.Errorf("send SIGHUP failed: %w", err)
	}
	return true, nil
}
