package script

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

	"github.com/infraboard/mcube/v2/ioc"
)

const (
	APP_NAME = "script_excutor"
)

func GetWorkDirAbsPath(workDir string) (string, error) {
	executor := ioc.Controller().Get(APP_NAME).(*ScriptExcutor)
	return executor.GetWorkDirAbsPath(workDir)
}

// ExecuteScript 执行脚本的接口函数
// 当前这个脚本引擎对象，别托管到ioc里了，直接暴露一个全局函数就好了，
// 毕竟这个函数是我们对外提供的接口，没必要让调用者关心它是怎么实现的
func ExecuteScript(ctx context.Context, in *ExecuteScriptRequest) (*ExecutionResult, error) {
	// 从IOC容器中获取ScriptExcutor实例
	executor := ioc.Controller().Get(APP_NAME).(*ScriptExcutor)
	return executor.ExecuteScript(ctx, in)
}

func NewExecuteScriptRequest(scriptPath string, args []string) *ExecuteScriptRequest {
	return &ExecuteScriptRequest{
		ScriptPath:          scriptPath,
		Args:                args,
		envVars:             make(map[string]string),
		createProcessGroup:  true, // 默认启用进程组管理
		useProcessGroupKill: true, // 默认使用进程组杀死方式
		timeout:             60 * time.Minute,
		logFile:             "stdout.log",
		resultFile:          "result.json",
	}
}

// ExecuteScriptRequest 定义了执行脚本所需的参数和配置
type ExecuteScriptRequest struct {
	ScriptPath string
	Args       []string
	Assets     []string // 资产目录列表

	// 脚本工作目录（默认当前目录）
	workDir string
	// 脚本执行环境变量（默认空）
	envVars map[string]string
	// 脚本执行日志文件路径（默认空，表示不记录日志）
	logFile string
	// 脚本执行超时时间（默认 0，表示不超时）
	timeout time.Duration
	// 脚本执行的命令元数据（可选），用于日志记录和监控
	metadata *CommandMetadata
	// 脚本执行结果文件路径（默认空，表示不保存结果）
	resultFile string
	// 需要收集内容的文件列表
	collectFiles []string

	// 日志回调函数, 用于实时输出日志（默认 nil，表示不使用回调）
	logCallback func(string)

	// 进程组管理控制参数, 用于避免脚本执行过程中，产生的子进程无法被正确杀死的问题
	// 是否创建新的进程组（默认 true，用于杀死子进程树）
	// 设为 false 时，子进程不会被放入新的进程组，不能被组杀
	createProcessGroup bool
	// 是否自定义 Cancel 函数以杀死进程组（默认 true）
	// 设为 false 时，使用默认的进程杀死方式
	useProcessGroupKill bool

	// 命令执行信息
	cmd *exec.Cmd
}

func (s *ExecuteScriptRequest) SetWorkDir(dir string) {
	s.workDir = dir
	if s.metadata != nil {
		s.metadata.WorkDir = dir
	}
}

// GetResultFilePath 获取结果文件路径
func (s *ExecuteScriptRequest) GetResultFilePath() string {
	if filepath.IsAbs(s.resultFile) {
		return s.resultFile
	}
	return filepath.Join(s.workDir, s.resultFile)
}

// SetTimeout 设置脚本执行超时时间
func (s *ExecuteScriptRequest) SetTimeout(timeout time.Duration) {
	s.timeout = timeout
	if s.metadata != nil {
		s.metadata.Timeout = timeout
	}
}

// SetLogFile 设置脚本执行日志文件路径
func (s *ExecuteScriptRequest) SetLogFile(path string) {
	s.logFile = path
}

func (s *ExecuteScriptRequest) SetLogCallback(callback func(string)) {
	s.logCallback = callback
}

// SetEnv 设置环境变量, key会被强制转换为大写
func (s *ExecuteScriptRequest) SetEnv(key, value string) {
	key = strings.ToUpper(strings.TrimSpace(key))
	s.envVars[key] = value
	if s.metadata != nil {
		s.metadata.EnvVars[key] = value
	}
}

// SetDebugScript 设置是否启用调试脚本
// 通过环境变量 DEBUG_SCRIPT 控制 (值为 true 或 1 时启用)
func (s *ExecuteScriptRequest) SetDebugScript(v bool) {
	if v {
		s.envVars["DEBUG_SCRIPT"] = "true"
	} else {
		s.envVars["DEBUG_SCRIPT"] = "false"
	}
}

// buildEnv 构建环境变量, 将请求中的环境变量与系统环境变量合并，返回一个新的环境变量列表
func (s *ExecuteScriptRequest) buildEnv() []string {
	env := os.Environ() // 获取系统环境变量

	// 补充自定义环境变量
	for key, value := range s.envVars {
		env = append(env, fmt.Sprintf("%s=%s", key, value))
	}

	return env
}

// isDebugScriptEnabled 检查是否启用调试脚本
// 通过环境变量 DEBUG_SCRIPT 控制 (值为 true 或 1 时启用)
func (s *ExecuteScriptRequest) isDebugScriptEnabled() bool {
	if val, ok := s.envVars["DEBUG_SCRIPT"]; ok {
		return strings.EqualFold(val, "true") || val == "1"
	}
	return false
}

func (r *ExecuteScriptRequest) WithWorkspacePrefix(workDirPrefix string) *ExecuteScriptRequest {
	if strings.HasPrefix(r.workDir, workDirPrefix) {
		return r
	}
	r.workDir = workDirPrefix + "/" + r.workDir
	return r
}

// getLogWriter 获取日志文件写入器
// 如果指定了日志回调函数，会创建一个多路写入器，将日志同时写入文件和回调函数
func (s *ExecuteScriptRequest) getLogWriter() (io.Writer, error) {
	logPath := s.logFile
	if !filepath.IsAbs(logPath) {
		logPath = filepath.Join(s.workDir, logPath)
	}

	// 如果文件存在, 清空文件内容
	if _, err := os.Stat(logPath); err == nil {
		if err := os.Truncate(logPath, 0); err != nil {
			return nil, fmt.Errorf("清空日志文件失败: %v", err)
		}
	}

	var logWriter io.Writer
	// 如果文件不存在，创建目录和文件, 存在则直接打开
	if _, err := os.Stat(logPath); os.IsNotExist(err) {
		if err := os.MkdirAll(filepath.Dir(logPath), 0755); err != nil {
			return nil, fmt.Errorf("创建日志目录失败: %v", err)
		}
		file, err := os.Create(logPath)
		if err != nil {
			return nil, fmt.Errorf("创建日志文件失败: %v", err)
		}
		logWriter = file
	} else {
		file, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
		if err != nil {
			return nil, fmt.Errorf("打开日志文件失败: %v", err)
		}
		logWriter = file
	}

	// 如果有回调函数，创建一个多路写入器
	if s.logCallback != nil {
		return io.MultiWriter(logWriter, &callbackWriter{callback: s.logCallback}), nil
	}

	return logWriter, nil
}

// callbackWriter 实现 io.Writer 接口的回调写入器
type callbackWriter struct {
	callback func(string)
}

func (c *callbackWriter) Write(p []byte) (n int, err error) {
	if c.callback != nil {
		c.callback(string(p))
	}
	return len(p), nil
}

// CommandMetadata 命令元数据
type CommandMetadata struct {
	ID          string            `json:"id"`                    // 命令唯一ID
	Name        string            `json:"name"`                  // 命令名称
	Description string            `json:"description,omitempty"` // 命令描述
	Tags        []string          `json:"tags,omitempty"`        // 标签
	CreatedBy   string            `json:"created_by"`            // 创建者
	CreatedAt   time.Time         `json:"created_at"`            // 创建时间
	Timeout     time.Duration     `json:"timeout"`               // 超时时间
	EnvVars     map[string]string `json:"env_vars"`              // 环境变量
	WorkDir     string            `json:"work_dir"`              // 工作目录
	RefTask     string            `json:"ref_task,omitempty"`    // 关联的任务
}

func (s *CommandMetadata) String() string {
	return fmt.Sprintf("ID=%s, Name=%s, CreatedBy=%s, CreatedAt=%s, Timeout=%s, WorkDir=%s",
		s.ID, s.Name, s.CreatedBy, s.CreatedAt.Format("2006-01-02 15:04:05"), s.Timeout.String(), s.WorkDir)
}

// VerifyScriptIntegrity 校验脚本完整性

// ExecutionResult 命令执行结果
type ExecutionResult struct {
	// 命令
	Command string `json:"command"`
	// 错误原因
	Error string `json:"error,omitempty"`
	// 命令退出码
	ExitCode int `json:"exit_code"`
	// 命令开始执行时间
	StartTime time.Time `json:"start_time"`
	// 命令结束执行时间
	EndTime *time.Time `json:"end_time"`
	// 命令执行时长
	Duration time.Duration `json:"duration"`
	// 命令执行是否成功
	Success bool `json:"success"`
	// 是否跳过执行（跳过视为成功，但标记为 Skip 以便管道状态同步）
	Skipped bool `json:"skipped,omitempty"`
	// 非错误的说明信息（比如跳过原因等）
	Message string `json:"message,omitempty"`
	// 元数据
	Metadata *CommandMetadata `json:"metadata"`
	// 文件内容集合
	FileContents map[string]string `json:"file_contents,omitempty"`
	// 脚本输出参数（供下一个任务使用）
	OutputParams map[string]string `json:"output_params,omitempty"`
}
