package runner

import (
	"context"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"time"

	"frpc-tool/internal/autostart"
	"frpc-tool/internal/util"
)

// Mode 描述操作采用的方式。
type Mode string

const (
	ModeService Mode = "service"
	ModeProcess Mode = "process"
)

// Runner 负责启动与停止 frpc。
type Runner struct {
	env  util.Environment
	auto autostart.Manager
}

// Sentinel errors for process control.
var (
	ErrNotRunning = errors.New("frpc process not running")
)

// New 创建 Runner。
func New(env util.Environment) *Runner {
	return &Runner{
		env:  env,
		auto: autostart.NewManager(env),
	}
}

// Start 启动 frpc，优先使用系统服务。
func (r *Runner) Start(ctx context.Context) (Mode, error) {
	if err := r.ensureBinaryAndConfig(); err != nil {
		return "", err
	}

	if r.auto != nil {
		installed, err := r.auto.IsInstalled(ctx)
		if err == nil && installed {
			if err := r.auto.Start(ctx); err != nil {
				return ModeService, err
			}
			return ModeService, nil
		}
	}

	if err := r.startProcess(ctx); err != nil {
		return ModeProcess, err
	}
	return ModeProcess, nil
}

// Stop 停止 frpc。
func (r *Runner) Stop(ctx context.Context) (Mode, error) {
	if r.auto != nil {
		installed, err := r.auto.IsInstalled(ctx)
		if err == nil && installed {
			if err := r.auto.Stop(ctx); err != nil {
				return ModeService, err
			}
			return ModeService, nil
		}
	}
	if err := r.stopProcess(); err != nil {
		if errors.Is(err, ErrNotRunning) {
			return ModeProcess, nil
		}
		return ModeProcess, err
	}
	return ModeProcess, nil
}

// Restart 重启 frpc。
func (r *Runner) Restart(ctx context.Context) (Mode, error) {
	mode, err := r.Stop(ctx)
	if err != nil && !errors.Is(err, os.ErrNotExist) && !errors.Is(err, ErrNotRunning) {
		return mode, err
	}
	return r.Start(ctx)
}

// Status 返回当前状态描述。
func (r *Runner) Status(ctx context.Context) (string, error) {
	if r.auto != nil {
		installed, err := r.auto.IsInstalled(ctx)
		if err == nil && installed {
			return r.auto.Status(ctx)
		}
	}

	pid, err := util.ReadPID(r.env.PIDFilePath)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return "frpc 未运行（PID 文件不存在）", nil
		}
		return "", err
	}
	if util.ProcessExists(pid) {
		return fmt.Sprintf("frpc 正在运行 (pid=%d)", pid), nil
	}
	_ = os.Remove(r.env.PIDFilePath)
	return "检测到遗留 PID 文件，已清理。frpc 未运行。", nil
}

func (r *Runner) ensureBinaryAndConfig() error {
	if _, err := os.Stat(r.env.BinaryPath); err != nil {
		if os.IsNotExist(err) {
			return fmt.Errorf("未找到 frpc 可执行文件，请先执行 update-binary")
		}
		return err
	}
	if _, err := os.Stat(r.env.ConfigPath); err != nil {
		if os.IsNotExist(err) {
			return fmt.Errorf("未找到配置文件，请先执行 deploy-config")
		}
		return err
	}
	return nil
}

func (r *Runner) startProcess(ctx context.Context) error {
	if _, err := os.Stat(r.env.PIDFilePath); err == nil {
		if pid, err := util.ReadPID(r.env.PIDFilePath); err == nil && util.ProcessExists(pid) {
			return fmt.Errorf("检测到 frpc 正在运行 (pid=%d)", pid)
		}
	}

	if err := util.EnsureDir(r.env.FrpcDir, 0o755); err != nil {
		return err
	}

	logFile, err := os.OpenFile(r.env.LogFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
	if err != nil {
		return fmt.Errorf("创建日志文件失败: %w", err)
	}
	defer logFile.Close()

	cmd := exec.CommandContext(ctx, r.env.BinaryPath, "-c", r.env.ConfigPath)
	cmd.Stdout = logFile
	cmd.Stderr = logFile

	if err := cmd.Start(); err != nil {
		return fmt.Errorf("启动 frpc 进程失败: %w", err)
	}
	pid := cmd.Process.Pid
	if err := util.WritePID(r.env.PIDFilePath, pid); err != nil {
		_ = cmd.Process.Kill()
		return fmt.Errorf("写入 PID 文件失败: %w", err)
	}
	// 释放子进程，避免僵尸进程。
	_ = cmd.Process.Release()
	return nil
}

func (r *Runner) stopProcess() error {
	pid, err := util.ReadPID(r.env.PIDFilePath)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return ErrNotRunning
		}
		return err
	}
	if !util.ProcessExists(pid) {
		_ = os.Remove(r.env.PIDFilePath)
		return ErrNotRunning
	}
	if err := util.KillProcess(pid); err != nil {
		return fmt.Errorf("发送终止信号失败: %w", err)
	}
	if !util.WaitProcessExit(pid, 5*time.Second) {
		if err := util.ForceKillProcess(pid); err != nil {
			return fmt.Errorf("强制终止进程失败: %w", err)
		}
	}
	if err := os.Remove(r.env.PIDFilePath); err != nil {
		return fmt.Errorf("删除 PID 文件失败: %w", err)
	}
	return nil
}
