package codeserver

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
	"time"

	"zlh-agent/internal/provision/executil"
	"zlh-agent/internal/state"
)

const (
	marker  = "/opt/zlh/.zlh/addons/code-server.installed"
	pidFile = "/opt/zlh/.zlh/addons/code-server.pid"
	binPath = "/opt/zlh/services/code-server/bin/code-server"
)

func Installed() bool {
	_, err := os.Stat(marker)
	return err == nil
}

func Running() bool {
	raw, err := os.ReadFile(pidFile)
	if err == nil {
		pid, convErr := strconv.Atoi(strings.TrimSpace(string(raw)))
		if convErr == nil && pid > 0 {
			process, findErr := os.FindProcess(pid)
			if findErr == nil && process.Signal(syscall.Signal(0)) == nil && pidMatchesCodeServer(pid) {
				return true
			}
		}
		_ = os.Remove(pidFile)
	}

	pid, err := findRunningPID()
	if err != nil || pid <= 0 {
		return false
	}
	_ = os.WriteFile(pidFile, []byte(strconv.Itoa(pid)), 0o644)
	return true
}

func Verify() error {

	if !Installed() {
		return fmt.Errorf("code-server addon marker missing")
	}

	if _, err := os.Stat(binPath); err != nil {
		return fmt.Errorf("code-server binary missing at %s", binPath)
	}

	if _, err := exec.LookPath("code-server"); err != nil {
		return fmt.Errorf("code-server binary not found in PATH")
	}

	return nil
}

func findRunningPID() (int, error) {
	matches, err := filepath.Glob("/proc/[0-9]*/cmdline")
	if err != nil {
		return 0, err
	}

	for _, match := range matches {
		raw, err := os.ReadFile(match)
		if err != nil || len(raw) == 0 {
			continue
		}

		cmdline := strings.ReplaceAll(string(raw), "\x00", " ")
		if !strings.Contains(cmdline, "code-server") {
			continue
		}
		if !strings.Contains(cmdline, "0.0.0.0:6000") {
			continue
		}

		pidStr := filepath.Base(filepath.Dir(match))
		pid, err := strconv.Atoi(pidStr)
		if err != nil || pid <= 0 {
			continue
		}
		return pid, nil
	}

	return 0, fmt.Errorf("code-server process not found")
}

func pidMatchesCodeServer(pid int) bool {
	if pid <= 0 {
		return false
	}
	raw, err := os.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "cmdline"))
	if err != nil || len(raw) == 0 {
		return false
	}
	cmdline := strings.ReplaceAll(string(raw), "\x00", " ")
	return strings.Contains(cmdline, "code-server") &&
		strings.Contains(cmdline, "0.0.0.0:6000")
}

func Start(cfg state.Config) error {
	if !Installed() {
		return fmt.Errorf("code-server addon not installed")
	}
	if Running() {
		return nil
	}
	if err := executil.RunEmbeddedScript("addons/codeserver/start.sh", launchEnv(cfg)...); err != nil {
		return err
	}
	if !Running() {
		return fmt.Errorf("code-server did not stay running")
	}
	return nil
}

func Stop() error {
	raw, err := os.ReadFile(pidFile)
	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}
		return fmt.Errorf("read code-server pid: %w", err)
	}

	pid, err := strconv.Atoi(strings.TrimSpace(string(raw)))
	if err != nil || pid <= 0 {
		_ = os.Remove(pidFile)
		return fmt.Errorf("invalid code-server pid")
	}

	process, err := os.FindProcess(pid)
	if err != nil {
		_ = os.Remove(pidFile)
		return fmt.Errorf("find code-server process: %w", err)
	}

	if err := process.Signal(syscall.SIGTERM); err != nil && !errorsIsProcessDone(err) {
		return fmt.Errorf("stop code-server: %w", err)
	}

	for i := 0; i < 20; i++ {
		if !Running() {
			_ = os.Remove(pidFile)
			return nil
		}
		time.Sleep(250 * time.Millisecond)
	}

	if err := process.Signal(syscall.SIGKILL); err != nil && !errorsIsProcessDone(err) {
		return fmt.Errorf("kill code-server: %w", err)
	}
	_ = os.Remove(pidFile)
	return nil
}

func Restart(cfg state.Config) error {
	if err := Stop(); err != nil {
		return err
	}
	return Start(cfg)
}

func errorsIsProcessDone(err error) bool {
	return err == os.ErrProcessDone || strings.Contains(strings.ToLower(err.Error()), "process already finished")
}
