package script

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	"github.com/infraboard/mcube/v2/ioc"
	"github.com/infraboard/mcube/v2/ioc/config/log"
	"github.com/rs/zerolog"
)

// 把ScriptExcutor 托管给IOC管理
func init() {
	ioc.Controller().Registry(&ScriptExcutor{
		WorkDirPrefix:           "workspace",
		ScriptDirPrefix:         "shells",
		IsVerifyScriptIntegrity: false,
		runningCommands:         make(map[string]*ExecuteScriptRequest),
	})
}

// ScriptExcutor 脚本执行器，负责制定脚本的执行逻辑，比如准备环境变量、执行脚本、处理输出等
type ScriptExcutor struct {
	ioc.ObjectImpl

	// 工作目录前缀，用于确保脚本执行在指定的目录下
	WorkDirPrefix string `toml:"work_dir_prefix" json:"work_dir_prefix" yaml:"work_dir_prefix"`
	// 脚本目录前缀，用于确保脚本路径在指定的目录下, 避免执行不安全的脚本
	ScriptDirPrefix string `toml:"script_dir_prefix" json:"script_dir_prefix" yaml:"script_dir_prefix"`
	// 是否启用脚本完整性校验, 通过编译时嵌入的脚本 hash 来校验脚本的完整性, 防止脚本被篡改
	IsVerifyScriptIntegrity bool `toml:"is_verify_script_integrity" json:"is_verify_script_integrity" yaml:"is_verify_script_integrity"`

	// 日志记录器，用于记录脚本执行的日志信息
	log *zerolog.Logger
	// 脚本完整性管理器，用于校验脚本的完整性
	integrityManager *ScriptIntegrityManager

	// 当前正在执行中的命令列表，key为工作目录，value为执行请求
	runningCommands map[string]*ExecuteScriptRequest
}

func (e *ScriptExcutor) Init() error {
	// 初始化日志记录器
	e.log = log.Sub(e.Name())
	// 初始化脚本完整性管理器
	e.integrityManager = NewScriptIntegrityManager(e.ScriptDirPrefix, e.IsVerifyScriptIntegrity)
	return nil
}

func (e *ScriptExcutor) Name() string {
	return APP_NAME
}

// ExecuteScript 执行脚本的核心方法，接受一个 ExecuteScriptRequest 请求，返回一个 ExecutionResult 结果
func (e *ScriptExcutor) ExecuteScript(ctx context.Context, in *ExecuteScriptRequest) (*ExecutionResult, error) {
	// 确保工作目录前缀, 避免脚本执行在不安全的目录下, 同时也方便清理执行结果
	in = in.WithWorkspacePrefix(e.WorkDirPrefix)

	// 确保工作目录存在
	if err := e.ensureWorkDir(in.workDir); err != nil {
		e.log.Error().Str("task_id", in.metadata.ID).Str("work_dir", in.workDir).Err(err).Msg("创建工作目录失败")
		return nil, err
	}

	// 确保元数据存在
	if in.metadata == nil {
		in.metadata = &CommandMetadata{
			ID:        generateID(),
			CreatedBy: getCurrentUser(),
			CreatedAt: time.Now(),
			WorkDir:   in.workDir,
			EnvVars:   in.envVars,
			Timeout:   in.timeout,
		}
	}

	// zerolog 的用法
	e.log.Info().Str("task_id", in.metadata.ID).Str("script_path", in.ScriptPath).Msg("准备执行脚本")

	// 脚本路径消毒
	scriptPath := e.WithPrefixScripPath(in.ScriptPath)
	fullScriptPath, err := e.sanitizeScriptPath(scriptPath)
	if err != nil {
		e.log.Error().Str("task_id", in.metadata.ID).Str("script_path", scriptPath).Err(err).Msg("脚本路径验证失败")
		return nil, err
	}
	e.log.Info().Str("task_id", in.metadata.ID).Str("full_script_path", fullScriptPath).Msg("脚本路径验证成功")

	// 校验脚本完整性
	if err := e.integrityManager.VerifyScript(fullScriptPath); err != nil {
		e.log.Error().Str("task_id", in.metadata.ID).Str("script_path", fullScriptPath).Err(err).Msg("脚本完整性校验失败")
		return nil, fmt.Errorf("脚本完整性校验失败: %v", err)
	}

	// 构建完整的shell命令
	shellArgs := []string{fullScriptPath}
	shellArgs = append(shellArgs, in.Args...)
	shellCommand := strings.Join(shellArgs, " ")

	e.log.Info().Str("task_id", in.metadata.ID).Str("command", shellCommand).Msg("脚本执行命令")

	// 注入标准变量
	e.InjectEnv(in)

	// 根据超时设置创建 context
	execCtx := ctx
	var cancel context.CancelFunc
	if in.timeout > 0 {
		execCtx, cancel = context.WithTimeout(ctx, in.timeout)
		defer cancel()
		e.log.Info().Str("task_id", in.metadata.ID).Dur("timeout", in.timeout).Msg("设置脚本执行超时时间")
	}

	// 创建命令
	cmd := exec.CommandContext(execCtx, "/bin/sh", "-c", shellCommand)
	cmd.Dir = in.workDir
	cmd.Env = in.buildEnv()
	// 根据参数设置进程组属性
	if in.createProcessGroup {
		cmd.SysProcAttr = &syscall.SysProcAttr{
			Setpgid: true, // 创建新的进程组
		}
	}

	// 根据参数设置自定义 Cancel 函数
	if in.useProcessGroupKill {
		// 自定义 Cancel 函数，确保杀死进程组
		cmd.Cancel = func() error {
			if cmd.Process == nil {
				return nil
			}
			// 使用负 PID 杀死整个进程组
			return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
		}
	}

	in.cmd = cmd

	// 脚本执行生成调试脚本（避免被脚本内部的 git clone 等操作清空）
	if in.isDebugScriptEnabled() {
		if debugScriptPath, writeErr := in.WriteDebugScript(fullScriptPath, in.Args); writeErr != nil {
			e.log.Warn().Str("task_id", in.metadata.ID).Err(writeErr).Msg("生成调试脚本失败")
		} else if debugScriptPath != "" {
			e.log.Info().Str("task_id", in.metadata.ID).Str("debug_script", debugScriptPath).Msg("已生成调试脚本，可进入工作目录执行 ./debug.sh 进行调试")
		}
	}

	// 执行脚本，并且把结果写到输出到文件里面
	// 开始执行命令

	// 添加到运行中的命令列表中
	e.runningCommands[in.workDir] = in
	defer delete(e.runningCommands, in.workDir)

	taskId := "unknown"
	if in.metadata != nil {
		taskId = in.metadata.ID
	}

	// 获取日志写入器
	logWriter, err := in.getLogWriter()
	if err != nil {
		e.log.Error().Str("task_id", taskId).Err(err).Msg("创建日志写入器失败")
		return nil, err
	}
	defer func() {
		if closer, ok := logWriter.(io.Closer); ok {
			closer.Close()
		}
	}()

	// 命令的日志输出到制定的地方
	in.cmd.Stdout = logWriter
	in.cmd.Stderr = logWriter

	// 记录执行开始, 包括时间、工作目录、脚本路径和元数据等信息
	startTime := time.Now()
	logHeader := fmt.Sprintf("\n=== Execution Started ===\nTime: %s\nWorkDir: %s\nScript: %s\nMetadata: %s\n",
		startTime.Format("2006-01-02 15:04:05"),
		in.workDir,
		fullScriptPath,
		in.metadata.String(),
	)
	fmt.Fprint(logWriter, logHeader)
	fmt.Fprint(logWriter, "\n")

	// 执行命令
	e.log.Info().Str("task_id", taskId).Msg("开始执行命令")
	err = in.cmd.Run()
	endTime := time.Now()
	duration := endTime.Sub(startTime)

	// 构建执行结果
	result := &ExecutionResult{
		Command:   fullScriptPath,
		StartTime: startTime,
		EndTime:   &endTime,
		Duration:  duration,
		Success:   err == nil,
		Metadata:  in.metadata,
	}

	if err != nil {
		// 判断是否为超时错误
		if in.cmd.ProcessState != nil && in.cmd.ProcessState.ExitCode() == -1 {
			e.log.Error().Str("task_id", taskId).Dur("duration", duration).Dur("timeout", in.timeout).Err(err).Msg("命令执行超时")
		} else {
			e.log.Error().Str("task_id", taskId).Dur("duration", duration).Err(err).Msg("命令执行失败")
		}

		// 错误信息
		result.Error = err.Error()
		if in.cmd.ProcessState != nil {
			result.ExitCode = in.cmd.ProcessState.ExitCode()
		} else {
			result.ExitCode = -1
		}
	} else {
		e.log.Info().Str("task_id", taskId).Dur("duration", duration).Msg("命令执行成功")
	}

	// 解析脚本输出参数（无论成功或失败都收集）
	outputFile := filepath.Join(in.workDir, "output.env")
	if outputParams, parseErr := ParseOutputParams(outputFile); parseErr == nil && len(outputParams) > 0 {
		result.OutputParams = outputParams
		e.log.Info().Str("task_id", taskId).Bool("success", result.Success).Int("param_count", len(outputParams)).Msg("成功解析脚本输出参数")
	} else if parseErr != nil && !os.IsNotExist(parseErr) {
		e.log.Warn().Str("task_id", taskId).Str("output_file", outputFile).Err(parseErr).Msg("解析输出参数文件失败")
	}

	// 保存执行结果(无论成功或失败都保存结果，方便后续查询和调试)
	e.log.Debug().Str("task_id", taskId).Str("result_file", in.GetResultFilePath()).Msg("准备保存执行结果")
	if saveErr := in.saveResult(result); saveErr != nil {
		e.log.Error().Str("task_id", taskId).Err(saveErr).Msg("保存执行结果失败")
	}

	// 记录执行结束
	logFooter := fmt.Sprintf("\n=== Execution Finished ===\nTime: %s\nDuration: %v\nSuccess: %t\nExitCode: %d\nError: %v\n",
		endTime.Format("2006-01-02 15:04:05"),
		duration,
		result.Success,
		result.ExitCode,
		err,
	)
	fmt.Fprint(logWriter, logFooter)
	return result, err
}

func (s *ScriptExcutor) GetWorkDirAbsPath(workDir string) (string, error) {
	// 如果是绝对路径，直接返回；如果是相对路径，基于工作目录前缀构建绝对路径
	if filepath.IsAbs(workDir) {
		return workDir, nil
	}
	return filepath.Abs(filepath.Join(s.WorkDirPrefix, workDir))
}

// saveResult 保存执行结果到文件
func (s *ExecuteScriptRequest) saveResult(result *ExecutionResult) error {
	if result == nil {
		return nil
	}

	resultPath := s.resultFile
	if !filepath.IsAbs(resultPath) {
		resultPath = filepath.Join(s.workDir, resultPath)
	}

	// 美化输出JSON
	data, err := json.MarshalIndent(result, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to marshal result: %v", err)
	}

	if err := os.WriteFile(resultPath, data, 0644); err != nil {
		return fmt.Errorf("failed to save result file: %v", err)
	}
	return nil
}

func (e *ScriptExcutor) InjectEnv(in *ExecuteScriptRequest) {
	// 注入标准变量
	in.SetEnv("SCRIPT_DIR", e.ScriptDirPrefix)
	in.SetEnv("WORKSPACE", in.workDir)

	// 创建 output.env 文件（用于脚本输出参数）
	outputEnvFile := filepath.Join(in.workDir, "output.env")
	// 创建空文件（如果已存在则清空）
	if f, err := os.Create(outputEnvFile); err == nil {
		f.Close()
		// 注入环境变量，让脚本可以直接使用
		in.SetEnv("OUTPUT_ENV_FILE", outputEnvFile)
	}
}

// ensureWorkDir 确保工作目录存在
func (e *ScriptExcutor) ensureWorkDir(workspace string) error {
	if workspace == "" {
		return fmt.Errorf("work directory cannot be empty")
	}

	if _, err := os.Stat(workspace); os.IsNotExist(err) {
		if err := os.MkdirAll(workspace, 0755); err != nil {
			return err
		}
	}
	return nil
}

func (r *ScriptExcutor) WithPrefixScripPath(scriptName string) string {
	return r.ScriptDirPrefix + "/" + scriptName
}

// sanitizeScriptPath 脚本路径消毒, 确保脚本路径在指定的目录下，并且文件存在且可执行
func (s *ScriptExcutor) sanitizeScriptPath(scriptPath string) (string, error) {
	if strings.TrimSpace(scriptPath) == "" {
		return "", fmt.Errorf("script path cannot be empty")
	}

	// 确保脚本路径在工作目录内
	scriptFullPath := scriptPath
	if !filepath.IsAbs(scriptPath) {
		// 直接获取绝对路径
		fullPath, err := filepath.Abs(scriptPath)
		if err != nil {
			return "", fmt.Errorf("failed to get absolute path for script path %s: %v", scriptFullPath, err)
		}
		scriptFullPath = fullPath
	}

	// 检查文件是否存在
	if _, err := os.Stat(scriptFullPath); os.IsNotExist(err) {
		return "", fmt.Errorf("script file does not exist: %s", scriptFullPath)
	}

	// 检查文件是否可执行
	info, err := os.Stat(scriptFullPath)
	if err != nil {
		return "", fmt.Errorf("failed to get script file info: %v", err)
	}

	// 如果是脚本文件，确保它有执行权限
	if info.Mode().Perm()&0111 == 0 {
		// 尝试添加执行权限
		if err := os.Chmod(scriptFullPath, info.Mode()|0111); err != nil {
			return "", fmt.Errorf("script file is not executable and cannot be made executable: %v", err)
		}
	}

	return scriptFullPath, nil
}
