package executor

import (
	"context"
	"fmt"
	"io"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"time"

	"github.com/creack/pty"
	"github.com/engigu/baihu-panel/internal/constant"
	"github.com/engigu/baihu-panel/internal/logger"
	"github.com/engigu/baihu-panel/internal/utils"
)

// Task 任务基础接口
type Task interface {
	GetID() string
	GetName() string
	GetCommand() string
	GetPreCommand() string
	GetPostCommand() string
	GetTimeout() int
	GetWorkDir() string
	GetEnvs() string
	GetEnvVars() []string
	GetLanguages() []map[string]string
	GetUseMise() bool
}

// CronTask 计划任务接口
type CronTask interface {
	Task
	GetSchedule() string
	UseMise() bool
	GetSecrets() []string
	GetRandomRange() int
}

// Request 任务执行请求
type Request struct {
	Command     string
	PreCommand  string
	PostCommand string
	WorkDir     string
	Envs        []string
	Timeout     int // 任务超时时间（分钟）
	Languages   []map[string]string
	UseMise     bool
}

// Result 任务执行结果
type Result struct {
	Output    string
	Error     string
	Status    string // 状态: success, failed
	Duration  int64  // 毫秒
	ExitCode  int
	StartTime time.Time
	EndTime   time.Time
}

// Hooks 执行钩子接口
type Hooks interface {
	// PreExecute 执行前钩子，返回日志ID和错误
	PreExecute(ctx context.Context, req Request) (logID string, err error)

	// PostExecute 执行后钩子，处理日志压缩和记录更新
	PostExecute(ctx context.Context, logID string, result *Result) error

	// OnHeartbeat 执行中心跳钩子，用于更新实时状态
	OnHeartbeat(ctx context.Context, logID string, duration int64) error
}

// Execute 执行命令（基础版本，不带钩子）
func Execute(ctx context.Context, req Request, stdout, stderr io.Writer) (*Result, error) {
	return ExecuteWithHooks(ctx, req, stdout, stderr, nil)
}

// ExecuteWithHooks 执行命令（带钩子支持）
func ExecuteWithHooks(ctx context.Context, req Request, stdout, stderr io.Writer, hooks Hooks) (*Result, error) {
	start := time.Now()

	// 演示模式拦截
	if constant.DemoMode {
		logger.Warnf("[Executor] 演示模式下已拦截命令执行: %s", req.Command)
		if stdout != nil {
			stdout.Write([]byte("\r\n\033[1;33m[演示模式] 命令执行已跳过\033[0m\r\n"))
		}

		// 仍然触发 PreExecute 以便流程完整
		var logID string
		if hooks != nil {
			logID, _ = hooks.PreExecute(ctx, req)
		}

		result := &Result{
			Status:    constant.TaskStatusFailed,
			Output:    "[演示模式] 该任务在演示模式下被禁用执行",
			StartTime: start,
			EndTime:   time.Now(),
		}

		if hooks != nil {
			hooks.PostExecute(ctx, logID, result)
		}
		return result, nil
	}

	// 2. 执行命令
	timeout := req.Timeout
	var execCtx context.Context
	var cancel context.CancelFunc

	if timeout > 0 {
		execCtx, cancel = context.WithTimeout(ctx, time.Duration(timeout)*time.Minute)
	} else {
		execCtx, cancel = context.WithCancel(ctx)
	}
	defer cancel()

	// 如果指定使用 mise，则预先构建好带 mise 的命令，这样 PreExecute 记录的就是完整命令
	if req.UseMise {
		utils.InjectNodePath(&req.Envs, req.Languages)
		req.Command = utils.BuildMiseCommand(req.Command, req.Languages)
		req.UseMise = false
	}

	// 组合指令（如果存在前置或后置指令）
	if req.PreCommand != "" || req.PostCommand != "" {
		finalCmd := ""
		if req.PreCommand != "" {
			finalCmd += req.PreCommand + "\n"
		}
		finalCmd += req.Command
		if req.PostCommand != "" {
			finalCmd += "\n" + req.PostCommand
		}
		req.Command = finalCmd
	}

	// 1. 执行前钩子
	var logID string
	if hooks != nil {
		id, err := hooks.PreExecute(ctx, req)
		if err != nil {
			return &Result{
				Status:    constant.TaskStatusFailed,
				Duration:  0,
				ExitCode:  1,
				StartTime: start,
				EndTime:   time.Now(),
			}, err
		}
		logID = id
	}

	shell, args := utils.GetShellCommand(req.Command)
	cmd := exec.CommandContext(execCtx, shell, args...)

	usePty := runtime.GOOS != "windows" && stdout != nil && (stdout == stderr || stdout == io.Discard)
	SetProcessGroupAndCancel(cmd, usePty)

	// 设置工作目录
	// 设置工作目录
	workDir := strings.TrimSpace(req.WorkDir)
	if workDir != "" {
		cmd.Dir = workDir
	}

	// 设置环境变量（始终继承系统环境变量）
	cmd.Env = os.Environ()
	if len(req.Envs) > 0 {
		cmd.Env = append(cmd.Env, req.Envs...)
	}
	// 强制注入终端环境标识及禁用输出缓冲的标志
	cmd.Env = append(cmd.Env,
		"TERM=xterm",
		"PYTHONUNBUFFERED=1",
		"NODE_NO_WARNINGS=1",
	)

	var pipeWriter *os.File
	var ptyFile *os.File
	var copyDone chan struct{}
	var err error

	var started bool
	// 尝试开启 PTY 模式（Unix/macOS 且输出合并时）
	if runtime.GOOS != "windows" && stdout != nil && (stdout == stderr || stdout == io.Discard) {
		// 强制注入终端环境标识及禁用输出缓冲的标志，确保 PTY 模式下最佳实时性能
		cmd.Env = append(cmd.Env,
			"TERM=xterm",
			"PYTHONUNBUFFERED=1",
			"NODE_NO_WARNINGS=1",
		)
		f, ptyErr := pty.Start(cmd)
		if ptyErr == nil {
			logger.Infof("[Executor] #%s 启动于 PTY 模式", logID)
			ptyFile = f
			started = true
			copyDone = make(chan struct{})
			go func() {
				defer close(copyDone)
				// io.Copy 对于 PTY 来说是最稳健且即时的流式拷贝
				io.Copy(stdout, f)
				f.Close()
			}()
		} else {
			logger.Errorf("[Executor] 任务 #%s PTY 启动失败: %v", logID, ptyErr)
		}
	}

	if !started {
		// 如果 stdout 和 stderr 指针不一致，但在逻辑上我们知道它们是同一个 MultiWriter，
		// 这里会显示为 Pipe 模式。
		if stdout != stderr && stdout != io.Discard {
			logger.Debugf("[Executor] 任务 #%d stdout (%p) 和 stderr (%p) 不同，回退到 Pipe 模式。", logID, stdout, stderr)
		}
		logger.Infof("[Executor] #%s 启动于 Pipe 模式", logID)
		if stdout != nil && stdout == stderr {
			pr, pw, err := os.Pipe()
			if err == nil {
				cmd.Stdout = pw
				cmd.Stderr = pw
				pipeWriter = pw
				copyDone = make(chan struct{})
				go func() {
					io.Copy(stdout, pr)
					pr.Close()
					close(copyDone)
				}()
			} else {
				cmd.Stdout = stdout
				cmd.Stderr = stderr
			}
		} else {
			cmd.Stdout = stdout
			cmd.Stderr = stderr
		}

		// 使用 cmd.Start() + Wait() 以便在后台处理心跳
		err = cmd.Start()
		if err != nil {
			if pipeWriter != nil {
				pipeWriter.Close()
			}
			// 启动失败的处理
			end := time.Now()
			result := &Result{
				Status:    constant.TaskStatusFailed,
				Duration:  end.Sub(start).Milliseconds(),
				ExitCode:  1,
				StartTime: start, // 记录开始时间
				EndTime:   end,
			}
			// 执行后钩子
			if hooks != nil {
				result.Output += "\n[系统错误] " + err.Error()
				hooks.PostExecute(ctx, logID, result)
			}
			return result, err
		}

		// 在父进程中关闭写端，这样子进程退出后 pr 才会收到 EOF
		if pipeWriter != nil {
			pipeWriter.Close()
		}
	} else {
		// PTY 模式下 cmd.Start() 已经在 pty.Start(cmd) 中调用过了
	}

	// 启动心跳协程
	done := make(chan struct{})
	go func() {
		// 每3秒一次心跳
		ticker := time.NewTicker(3 * time.Second)
		defer ticker.Stop()
		for {
			select {
			case <-done:
				return
			case <-ticker.C:
				if hooks != nil {
					hooks.OnHeartbeat(ctx, logID, time.Since(start).Milliseconds())
				}
			}
		}
	}()

	// 等待命令完成
	err = cmd.Wait()
	close(done) // 停止心跳

	// PTY 模式下需要显式关闭
	if ptyFile != nil {
		ptyFile.Close()
	}

	// 等待日志复制完成
	if copyDone != nil {
		<-copyDone
	}

	end := time.Now()

	result := &Result{
		StartTime: start,
		EndTime:   end,
		Duration:  end.Sub(start).Milliseconds(),
	}

	if err != nil {
		result.Status = constant.TaskStatusFailed
		result.Error = err.Error()
		if exitErr, ok := err.(*exec.ExitError); ok {
			result.ExitCode = exitErr.ExitCode()
		} else {
			result.ExitCode = 1
		}
	} else {
		result.Status = constant.TaskStatusSuccess
		result.ExitCode = 0
	}

	// 3. 执行后钩子
	if hooks != nil {
		if hookErr := hooks.PostExecute(ctx, logID, result); hookErr != nil {
			// 记录钩子错误但不影响执行结果
			result.Output += "\n[钩子错误] " + hookErr.Error()
		}
	}

	return result, err
}

// ParseEnvVars 解析环境变量字符串 "KEY1=VALUE1,KEY2=VALUE2"
func ParseEnvVars(envStr string) []string {
	if envStr == "" {
		return nil
	}

	pairs := strings.Split(envStr, ",")
	result := make([]string, 0, len(pairs))

	for _, pair := range pairs {
		if pair == "" {
			continue
		}
		// 解码特殊字符
		pair = strings.ReplaceAll(pair, "{{COMMA}}", ",")
		pair = strings.ReplaceAll(pair, "{{EQUAL}}", "=")
		pair = strings.ReplaceAll(pair, "{{NL}}", "\n")
		result = append(result, pair)
	}

	return result
}

// FormatEnvVars 将环境变量列表格式化为逗号分隔的字符串 "KEY1=VALUE1,KEY2=VALUE2"
// 会对 , 和 = 以及换行符进行转义
func FormatEnvVars(envs []string) string {
	if len(envs) == 0 {
		return ""
	}

	pairs := make([]string, 0, len(envs))
	for _, pair := range envs {
		// 寻找第一个等号
		idx := strings.Index(pair, "=")
		if idx == -1 {
			continue
		}
		name := pair[:idx]
		value := pair[idx+1:]

		// 转义特殊字符
		encodedValue := strings.ReplaceAll(value, ",", "{{COMMA}}")
		encodedValue = strings.ReplaceAll(encodedValue, "=", "{{EQUAL}}")
		encodedValue = strings.ReplaceAll(encodedValue, "\n", "{{NL}}")
		pairs = append(pairs, fmt.Sprintf("%s=%s", name, encodedValue))
	}

	return strings.Join(pairs, ",")
}
