package mihomo

import (
	"embed"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"sync"
	"time"

	"gopkg.in/yaml.v3"
)

var (
	mu        sync.Mutex
	process   *os.Process
	running   bool
	configDir string

	logMu   sync.Mutex
	logRing [500]string
	logLen  int
	logPos  int
)

//go:embed default.yaml
var defaultConfigFS embed.FS

func init() {
	configDir = defaultConfigDir()
}

func defaultConfigDir() string {
	exe, err := os.Executable()
	if err != nil {
		return "/lib/mihomo"
	}
	if resolved, err := filepath.EvalSymlinks(exe); err == nil {
		exe = resolved
	}
	base := filepath.Dir(exe)
	if base == "/usr/bin" || base == "/usr/sbin" || base == "/sbin" || base == "/bin" {
		return "/lib/mihomo"
	}
	return filepath.Join(base, "mihomo")
}

func SetConfigDir(dir string) {
	mu.Lock()
	defer mu.Unlock()
	configDir = dir
}

func ConfigDir() string {
	return configDir
}

func DataDir() string {
	return filepath.Join(ConfigDir(), "data")
}

func CoresDir() string {
	return filepath.Join(ConfigDir(), "cores")
}

func ConfigPath() string {
	return filepath.Join(DataDir(), "config.yaml")
}

func CorePath() string {
	arch := runtime.GOARCH
	switch arch {
	case "amd64":
		arch = "amd64"
	case "arm64":
		arch = "arm64"
	case "arm":
		arch = "armv7"
	default:
		arch = "amd64"
	}
	return filepath.Join(CoresDir(), fmt.Sprintf("mihomo-linux-%s", arch))
}

func EnsureDefaultConfig() error {
	cfgPath := ConfigPath()
	if _, err := os.Stat(cfgPath); err == nil {
		return nil
	}
	if err := os.MkdirAll(DataDir(), 0755); err != nil {
		return fmt.Errorf("mkdir mihomo data dir: %w", err)
	}
	data, err := defaultConfigFS.ReadFile("default.yaml")
	if err != nil {
		return fmt.Errorf("read default config: %w", err)
	}
	tmp := cfgPath + ".tmp"
	if err := os.WriteFile(tmp, data, 0644); err != nil {
		return fmt.Errorf("write default config: %w", err)
	}
	if err := os.Rename(tmp, cfgPath); err != nil {
		return fmt.Errorf("rename default config: %w", err)
	}
	return nil
}

func appendLog(line string) {
	logMu.Lock()
	defer logMu.Unlock()
	logRing[logPos%len(logRing)] = line
	logPos++
	if logLen < len(logRing) {
		logLen++
	}
}

func Logs() []string {
	logMu.Lock()
	defer logMu.Unlock()
	result := make([]string, 0, logLen)
	start := logPos - logLen
	for i := start; i < logPos; i++ {
		result = append(result, logRing[i%len(logRing)])
	}
	return result
}

type lineWriter struct{}

func (lineWriter) Write(p []byte) (int, error) {
	start := 0
	for i, b := range p {
		if b == '\n' {
			line := string(p[start:i])
			if line != "" {
				appendLog(line)
			}
			start = i + 1
		}
	}
	if start < len(p) {
		line := string(p[start:])
		if line != "" {
			appendLog(line)
		}
	}
	return len(p), nil
}

func Status() map[string]interface{} {
	mu.Lock()
	defer mu.Unlock()
	corePath := CorePath()
	_, err := os.Stat(corePath)
	coreExists := err == nil

	_, cfgErr := os.Stat(ConfigPath())
	cfgExists := cfgErr == nil

	status := map[string]interface{}{
		"running":       running,
		"core_exists":   coreExists,
		"core_path":     corePath,
		"config_dir":    DataDir(),
		"config_file":   ConfigPath(),
		"config_exists": cfgExists,
	}

	if running && process != nil {
		status["pid"] = process.Pid
	}

	return status
}

func Start() error {
	mu.Lock()
	defer mu.Unlock()

	if running {
		return fmt.Errorf("mihomo is already running")
	}

	corePath := CorePath()
	if _, err := os.Stat(corePath); err != nil {
		return fmt.Errorf("mihomo core not found at %s: %w", corePath, err)
	}

	cfgPath := ConfigPath()
	if _, err := os.Stat(cfgPath); err != nil {
		return fmt.Errorf("mihomo config not found at %s: %w", cfgPath, err)
	}

	cmd := exec.Command(corePath, "-d", DataDir())
	lw := lineWriter{}
	w := io.MultiWriter(os.Stdout, lw)
	cmd.Stdout = w
	cmd.Stderr = io.MultiWriter(os.Stderr, lw)

	if err := cmd.Start(); err != nil {
		return fmt.Errorf("start mihomo: %w", err)
	}

	process = cmd.Process
	running = true

	go func() {
		err := cmd.Wait()
		mu.Lock()
		running = false
		process = nil
		mu.Unlock()
		if err != nil {
			fmt.Fprintf(os.Stderr, "mihomo process exited: %v\n", err)
		}
	}()

	time.Sleep(500 * time.Millisecond)
	if !running {
		return fmt.Errorf("mihomo exited immediately")
	}

	return nil
}

func Stop() error {
	mu.Lock()
	defer mu.Unlock()

	if !running || process == nil {
		running = false
		process = nil
		return nil
	}

	if err := process.Signal(os.Interrupt); err != nil {
		_ = process.Kill()
	}
	running = false
	process = nil
	return nil
}

func Restart() error {
	if err := Stop(); err != nil {
		return err
	}
	time.Sleep(500 * time.Millisecond)
	return Start()
}

func IsRunning() bool {
	mu.Lock()
	defer mu.Unlock()
	return running
}

func InstallCore(srcPath string) error {
	arch := runtime.GOARCH
	switch arch {
	case "amd64":
		arch = "amd64"
	case "arm64":
		arch = "arm64"
	case "arm":
		arch = "armv7"
	default:
		arch = "amd64"
	}
	dstPath := filepath.Join(CoresDir(), fmt.Sprintf("mihomo-linux-%s", arch))

	if err := os.MkdirAll(CoresDir(), 0755); err != nil {
		return fmt.Errorf("mkdir cores: %w", err)
	}

	data, err := os.ReadFile(srcPath)
	if err != nil {
		return fmt.Errorf("read core source: %w", err)
	}

	if err := os.WriteFile(dstPath, data, 0755); err != nil {
		return fmt.Errorf("write core: %w", err)
	}

	return nil
}

func LoadConfig() (map[string]interface{}, error) {
	data, err := os.ReadFile(ConfigPath())
	if err != nil {
		if os.IsNotExist(err) {
			return map[string]interface{}{}, nil
		}
		return nil, fmt.Errorf("read mihomo config: %w", err)
	}
	var cfg map[string]interface{}
	if err := yaml.Unmarshal(data, &cfg); err != nil {
		return nil, fmt.Errorf("parse mihomo config: %w", err)
	}
	return cfg, nil
}

func SaveConfig(cfg map[string]interface{}) error {
	data, err := yaml.Marshal(cfg)
	if err != nil {
		return fmt.Errorf("marshal mihomo config: %w", err)
	}
	if err := os.MkdirAll(DataDir(), 0755); err != nil {
		return fmt.Errorf("mkdir mihomo data dir: %w", err)
	}
	tmp := ConfigPath() + ".tmp"
	if err := os.WriteFile(tmp, data, 0644); err != nil {
		return fmt.Errorf("write mihomo config: %w", err)
	}
	if err := os.Rename(tmp, ConfigPath()); err != nil {
		return fmt.Errorf("rename mihomo config: %w", err)
	}
	return nil
}