package pkg

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"reflect"
	"strings"
	"time"

	"github.com/AlexanderGrooff/jinja-go"
	"github.com/AlexanderGrooff/spage/pkg/common"
	"github.com/AlexanderGrooff/spage/pkg/config"

	// Remove pongo2 import if no longer needed directly here
	// "github.com/flosch/pongo2"
	"github.com/AlexanderGrooff/spage/pkg/daemon"
)

type TaskStatus string

const (
	TaskStatusSkipped TaskStatus = "skipped"
	TaskStatusFailed  TaskStatus = "failed"
	TaskStatusChanged TaskStatus = "changed"
	TaskStatusOk      TaskStatus = "ok"
)

func (s TaskStatus) String() string {
	return string(s)
}

// Useful for having a single type to pass around in channels
type TaskResult struct {
	Output   ModuleOutput
	Error    error // This can now be nil, a normal error, or an IgnoredTaskError
	Closure  *Closure
	Task     GraphNode
	Duration time.Duration
	Status   TaskStatus
	Failed   bool
	Changed  bool
	// ExecutionSpecificOutput can store runner-specific results, e.g., SpageActivityResult for Temporal.
	ExecutionSpecificOutput interface{}
}

// IgnoredTaskError is a custom error type used when a task fails
// but IgnoreErrors is set to true.
// It wraps the original error.
type IgnoredTaskError struct {
	OriginalErr error
}

// Error implements the error interface for IgnoredTaskError.
func (e *IgnoredTaskError) Error() string {
	if e.OriginalErr != nil {
		return fmt.Sprintf("ignored: %s", e.OriginalErr.Error())
	}
	return "ignored error (no original error specified)"
}

// Unwrap allows errors.Is and errors.As to work with the wrapped original error.
func (e *IgnoredTaskError) Unwrap() error {
	return e.OriginalErr
}

// FactProvider is an interface that module outputs can implement
// to provide a map representation suitable for registering as facts.
type FactProvider interface {
	AsFacts() map[string]interface{}
}

type TaskParams struct {
	Id           int                 `yaml:"id" json:"id"`
	Name         string              `yaml:"name" json:"name"`
	Module       string              `yaml:"module" json:"module"`
	Params       ModuleInput         `yaml:"params" json:"params"`
	Validate     string              `yaml:"validate" json:"validate,omitempty"`
	Before       string              `yaml:"before" json:"before,omitempty"`
	After        string              `yaml:"after" json:"after,omitempty"`
	When         JinjaExpressionList `yaml:"when" json:"when,omitempty"`
	Register     string              `yaml:"register" json:"register,omitempty"`
	Become       bool                `yaml:"become" json:"become,omitempty"`
	BecomeUser   string              `yaml:"become_user" json:"become_user,omitempty"`
	IgnoreErrors JinjaExpression     `yaml:"ignore_errors,omitempty" json:"ignore_errors,omitempty"`
	FailedWhen   JinjaExpressionList `yaml:"failed_when,omitempty" json:"failed_when,omitempty"`
	ChangedWhen  JinjaExpressionList `yaml:"changed_when,omitempty" json:"changed_when,omitempty"`
	Loop         interface{}         `yaml:"loop,omitempty" json:"loop,omitempty"`
	DelegateTo   string              `yaml:"delegate_to,omitempty" json:"delegate_to,omitempty"`
	RunOnce      JinjaExpression     `yaml:"run_once,omitempty" json:"run_once,omitempty"`
	NoLog        JinjaExpression     `yaml:"no_log,omitempty" json:"no_log,omitempty"`
	Tags         []string            `yaml:"tags,omitempty" json:"tags,omitempty"`
	Vars         interface{}         `yaml:"vars,omitempty" json:"vars,omitempty"`
	Rescue       []GraphNode         `yaml:"rescue,omitempty" json:"rescue,omitempty"`
	Always       []GraphNode         `yaml:"always,omitempty" json:"always,omitempty"`

	Until   JinjaExpression `yaml:"until,omitempty" json:"until,omitempty"`
	Retries int             `yaml:"retries,omitempty" json:"retries,omitempty"`
	Delay   int             `yaml:"delay,omitempty" json:"delay,omitempty"`

	CheckMode *bool    `yaml:"check_mode,omitempty" json:"check_mode,omitempty"`
	Diff      *bool    `yaml:"diff,omitempty" json:"diff,omitempty"`
	Notify    []string `yaml:"notify,omitempty" json:"notify,omitempty"`
	IsHandler bool     `yaml:"is_handler,omitempty" json:"is_handler,omitempty"`

	// Role context information for resource resolution
	RoleName string `yaml:"_role_name,omitempty" json:"_role_name,omitempty"`
	RolePath string `yaml:"_role_path,omitempty" json:"_role_path,omitempty"`
}

func (tp *TaskParams) MarshalJSON() ([]byte, error) {
	// Use a type alias to avoid recursion when marshaling other fields.
	type Alias TaskParams
	// Create an auxiliary struct to handle standard fields and the special Params field.
	aux := &struct {
		*Alias
		Params json.RawMessage `json:"params,omitempty"` // Use RawMessage to hold pre-marshaled params
	}{
		Alias: (*Alias)(tp),
	}

	// Marshal the actual parameters stored in t.Params.Actual.
	var paramsBytes []byte
	var err error
	if tp.Params.Actual != nil {
		paramsBytes, err = json.Marshal(tp.Params.Actual)
		if err != nil {
			return nil, fmt.Errorf("failed to marshal Task.Params.Actual for module %s: %w", tp.Module, err)
		}
	} else {
		// If Actual is nil, represent params as JSON null or an empty object.
		// An empty object {} might be safer for downstream compatibility.
		paramsBytes = []byte("{}")
	}
	aux.Params = paramsBytes // Assign the marshaled bytes

	// Marshal the auxiliary struct which now has correctly marshaled params.
	return json.Marshal(aux)
}

func (tp *TaskParams) UnmarshalJSON(data []byte) error {
	type Alias TaskParams // Use type alias to avoid recursion during unmarshaling
	aux := &struct {
		Params json.RawMessage `json:"params"`
		*Alias
	}{
		Alias: (*Alias)(tp),
	}

	if err := json.Unmarshal(data, &aux); err != nil {
		return fmt.Errorf("failed to unmarshal task aux struct: %w", err)
	}

	// If Module or Params are not present, or params is null, no further processing for Params.Actual
	if tp.Module == "" || len(aux.Params) == 0 || string(aux.Params) == "null" {
		tp.Params.Actual = nil // Ensure Actual is nil if no params or module
		return nil
	}

	mod, ok := GetModule(tp.Module)
	if !ok {
		return fmt.Errorf("module %s not found during task JSON unmarshaling", tp.Module)
	}

	inputType := mod.InputType()
	// Create a new instance of the specific module input type (e.g., *ShellInput)
	// inputVal will be a pointer to the zero value of the input type.
	inputValPtr := reflect.New(inputType)

	// Unmarshal the raw JSON params into this specific input type instance (pointer)
	if err := json.Unmarshal(aux.Params, inputValPtr.Interface()); err != nil {
		return fmt.Errorf("failed to unmarshal params for module %s: %w", tp.Module, err)
	}

	// inputValPtr is the pointer (e.g., *ShellInput). Get the value it points to.
	elemValue := inputValPtr.Elem()

	// Check if the VALUE type implements the interface.
	actualParamProvider, ok := elemValue.Interface().(ConcreteModuleInputProvider)
	if ok {
		// If the value type implements it, store the value.
		tp.Params.Actual = actualParamProvider
	} else {
		// Check if the POINTER type implements the interface (less common case for modules).
		actualParamProvider, ok = inputValPtr.Interface().(ConcreteModuleInputProvider)
		if ok {
			// If the pointer type implements it, store the pointer.
			tp.Params.Actual = actualParamProvider
		} else {
			// Neither value nor pointer implements the interface - should not happen for registered modules.
			return fmt.Errorf("module %s params type %T (or its pointer) does not implement ConcreteModuleInputProvider", tp.Module, elemValue.Interface())
		}
	}

	return nil
}

func (tp *TaskParams) ToCode() string {
	var sb strings.Builder
	// Get the code representation of the actual parameters
	actualParamsCode := "nil" // Default to nil if Actual is not populated
	if tp.Params.Actual != nil {
		actualParamsCode = tp.Params.Actual.ToCode()
	}

	// Construct the Task literal, wrapping the actual params code in pkg.ModuleInput
	sb.WriteString(fmt.Sprintf("&pkg.TaskParams{Id: %d, Name: %q, Module: %q, Register: %q, Params: pkg.ModuleInput{Actual: %s}",
		tp.Id,
		tp.Name,
		tp.Module,
		tp.Register,
		actualParamsCode, // Use the generated code for the Actual field
	))

	if len(tp.When) > 0 {
		sb.WriteString(fmt.Sprintf(", When: %s", tp.When.ToCode()))
	}
	if tp.Become {
		sb.WriteString(fmt.Sprintf(", Become: %t", tp.Become))
	}
	if tp.BecomeUser != "" {
		sb.WriteString(fmt.Sprintf(", BecomeUser: %q", tp.BecomeUser))
	}

	if tp.IgnoreErrors.Expression != "" {
		sb.WriteString(fmt.Sprintf(", IgnoreErrors: %s", tp.IgnoreErrors.ToCode()))
	}
	if tp.CheckMode != nil {
		// Create a pointer to a boolean for the generated code
		sb.WriteString(fmt.Sprintf(", CheckMode: func() *bool { b := %t; return &b }()", *tp.CheckMode))
	}
	if tp.Diff != nil {
		sb.WriteString(fmt.Sprintf(", Diff: func() *bool { b := %t; return &b }()", *tp.Diff))
	}
	if tp.FailedWhen != nil {
		sb.WriteString(fmt.Sprintf(", FailedWhen: %s", tp.FailedWhen.ToCode()))
	}
	if tp.ChangedWhen != nil {
		sb.WriteString(fmt.Sprintf(", ChangedWhen: %s", tp.ChangedWhen.ToCode()))
	}
	if tp.DelegateTo != "" {
		sb.WriteString(fmt.Sprintf(", DelegateTo: %q", tp.DelegateTo))
	}
	if tp.RunOnce.Expression != "" {
		sb.WriteString(fmt.Sprintf(", RunOnce: %s", tp.RunOnce.ToCode()))
	}
	if tp.IsHandler {
		sb.WriteString(fmt.Sprintf(", IsHandler: %t", tp.IsHandler))
	}
	if tp.Vars != nil {
		sb.WriteString(fmt.Sprintf(", Vars: %#v", tp.Vars))
	}
	if tp.Until.Expression != "" {
		sb.WriteString(fmt.Sprintf(", Until: %s", tp.Until.ToCode()))
	}
	if tp.Retries > 0 {
		sb.WriteString(fmt.Sprintf(", Retries: %d", tp.Retries))
	}
	if tp.Delay > 0 {
		sb.WriteString(fmt.Sprintf(", Delay: %d", tp.Delay))
	}
	if len(tp.Tags) > 0 {
		sb.WriteString(", Tags: []string{")
		for i, tag := range tp.Tags {
			sb.WriteString(fmt.Sprintf("%q", tag))
			if i < len(tp.Tags)-1 {
				sb.WriteString(", ")
			}
		}
		sb.WriteString("}")
	}
	if len(tp.Notify) > 0 {
		sb.WriteString(", Notify: []string{")
		for i, handler := range tp.Notify {
			sb.WriteString(fmt.Sprintf("%q", handler))
			if i < len(tp.Notify)-1 {
				sb.WriteString(", ")
			}
		}
		sb.WriteString("}")
	}
	// Handle Loop field in ToCode: generate code for string or slice.
	switch v := tp.Loop.(type) {
	case string:
		if v != "" {
			sb.WriteString(fmt.Sprintf(", Loop: %q", v))
		}
	case []interface{}:
		if len(v) > 0 {
			sb.WriteString(", Loop: []interface{}{")
			for i, item := range v {
				sb.WriteString(fmt.Sprintf("%#v", item)) // Use %#v for Go syntax representation
				if i < len(v)-1 {
					sb.WriteString(", ")
				}
			}
			sb.WriteString("}")
		}
		// Add cases for other expected list types if necessary (e.g., []string)
	}
	if tp.RolePath != "" {
		sb.WriteString(fmt.Sprintf(", RolePath: %q", tp.RolePath))
	}
	if tp.RoleName != "" {
		sb.WriteString(fmt.Sprintf(", RoleName: %q", tp.RoleName))
	}

	sb.WriteString("}")
	return sb.String()
}

func (t TaskParams) GetVariableUsage() ([]string, error) {
	varsUsage, err := GetVariableUsageFromModule(t.Params.Actual)
	if err != nil {
		return nil, fmt.Errorf("error getting variable usage from module: %w", err)
	}

	tVal := reflect.ValueOf(t)
	tType := reflect.TypeOf(t)
	for i := 0; i < tVal.NumField(); i++ {
		field := tVal.Field(i)
		fieldType := tType.Field(i)
		fieldName := fieldType.Name

		if fieldName == "Params" || fieldName == "Id" || fieldName == "Name" || fieldName == "Module" || fieldName == "Register" || fieldName == "IsHandler" || fieldName == "Tags" || fieldName == "Notify" {
			continue
		}

		if field.Kind() == reflect.String {
			str := field.String()
			if str != "" {
				// Skip variable extraction for unused variables (e.g., default() patterns)
				if CheckIfVariableIsUnused(str) {
					common.LogDebug("Skipping variable extraction for unused variable", map[string]interface{}{"variable": str})
					continue
				}
				varsUsage = append(varsUsage, GetVariableUsageFromTemplate(str)...)
			}
			continue
		}

		if field.Kind() == reflect.Slice && field.Type().Elem().Kind() == reflect.String {
			for j := 0; j < field.Len(); j++ {
				str := field.Index(j).String()
				if str != "" {
					varsUsage = append(varsUsage, GetVariableUsageFromTemplate(str)...)
				}
			}
			continue
		}

		if field.Kind() == reflect.Interface && !field.IsNil() {
			val := field.Interface()
			switch v := val.(type) {
			case string:
				varsUsage = append(varsUsage, GetVariableUsageFromTemplate(v)...)
			case []interface{}:
				for _, item := range v {
					if s, ok := item.(string); ok {
						varsUsage = append(varsUsage, GetVariableUsageFromTemplate(s)...)
					}
				}
			case map[string]interface{}:
				for _, vv := range v {
					if s, ok := vv.(string); ok {
						varsUsage = append(varsUsage, GetVariableUsageFromTemplate(s)...)
					}
				}
			}
			continue
		}

		// Explicitly handle Jinja expression types on the Task struct
		if field.CanInterface() {
			switch v := field.Interface().(type) {
			case JinjaExpression:
				if v.Expression != "" {
					vars, _ := jinja.ParseVariablesFromExpression(v.Expression)
					varsUsage = append(varsUsage, vars...)
				}
				continue
			case JinjaExpressionList:
				for _, expr := range v {
					if expr.Expression != "" {
						vars, _ := jinja.ParseVariablesFromExpression(expr.Expression)
						varsUsage = append(varsUsage, vars...)
					}
				}
				continue
			}
		}
	}

	varsUsage = common.RemoveFromSlice(varsUsage, "item")

	unique := make(map[string]struct{})
	for _, v := range varsUsage {
		unique[v] = struct{}{}
	}
	result := make([]string, 0, len(unique))
	for k := range unique {
		result = append(result, k)
	}
	if len(result) > 0 {
		common.LogDebug("Task variable usage", map[string]interface{}{"task": t.Name, "usage": result})
	}
	return result, nil
}

type Task struct {
	*TaskParams
}

func (t Task) Params() *TaskParams {
	return t.TaskParams
}

// UnmarshalJSON implements the json.Unmarshaler interface for Task.
func (t *Task) UnmarshalJSON(data []byte) error {
	return json.Unmarshal(data, &t.TaskParams)
}

// MarshalJSON implements the json.Marshaler interface for Task.
// This ensures that the Params field is marshaled correctly by handling the Actual field.
func (t Task) MarshalJSON() ([]byte, error) {
	return json.Marshal(struct {
		*TaskParams
	}{
		TaskParams: t.TaskParams,
	})
}

func (t Task) ToCode() string {
	return fmt.Sprintf("&pkg.Task{TaskParams: %s}", t.TaskParams.ToCode())
}

func (t Task) String() string {
	return t.Name
}

// Conform to GraphNode interface
func (t Task) GetId() int {
	return t.Id
}

func (t *Task) SetId(id int) {
	t.Id = id
}

func (t Task) GetName() string {
	return t.Name
}

func (t Task) GetIsHandler() bool {
	return t.IsHandler
}

func (t Task) GetTags() []string {
	return t.Tags
}

func (t Task) GetVariableUsage() ([]string, error) {
	return t.TaskParams.GetVariableUsage()
}

func (t Task) ConstructClosure(c *HostContext, cfg *config.Config) *Closure {
	closure := Closure{
		HostContext: c,
		Config:      cfg,
		ExtraFacts:  make(map[string]interface{}),
	}

	if t.Loop != nil {
		closure.ExtraFacts["item"] = t.Loop
	}
	if t.Vars != nil {
		switch t.Vars.(type) {
		case string:
			vars, err := jinja.ParseVariables(t.Vars.(string))
			if err != nil {
				log.Fatalf("Failed to parse vars: %v", err)
			}
			for _, v := range vars {
				closure.ExtraFacts[v] = t.Vars
			}
		case map[string]interface{}:
			for k, v := range t.Vars.(map[string]interface{}) {
				closure.ExtraFacts[k] = v
			}
		default:
			log.Fatalf("Invalid vars type: %T", t.Vars)
		}
	}

	// Add role context to closure if available
	if t.RoleName != "" {
		closure.ExtraFacts["_spage_role_name"] = t.RoleName
	}
	if t.RolePath != "" {
		closure.ExtraFacts["_spage_role_path"] = t.RolePath
	}

	return &closure
}

func (t Task) ShouldExecute(closure *Closure) (bool, error) {
	if len(t.When) > 0 {
		// Evaluate truthiness using the helper function
		conditionMet := t.When.IsEmpty() || t.When.IsTruthy(closure)

		// Log the evaluation result
		common.DebugOutput("Evaluated when condition %q -> %t", t.When, conditionMet)

		// Return the evaluated truthiness
		return conditionMet, nil
	}
	// If no 'when' condition, always execute
	return true, nil
}

func (t Task) ExecuteModule(closure *Closure) chan TaskResult {
	ch := make(chan TaskResult, 1)
	var daemonClient *daemon.Client
	if client, ok := closure.Config.GetDaemonReporting().(*daemon.Client); ok {
		daemonClient = client
	}
	_ = ReportTaskStart(daemonClient, t.Id, t.Name, closure.HostContext.Host.Name, 0)

	startTime := time.Now()

	shouldExecute, err := t.ShouldExecute(closure)
	if err != nil {
		res := TaskResult{Task: t, Closure: closure, Status: TaskStatusFailed, Error: err}
		_ = ReportTaskCompletion(daemonClient, t, res, closure.HostContext.Host.Name, -1)
		ch <- res
		return ch
	}
	if !shouldExecute {
		common.LogDebug("Skipping execution of task due to 'when' condition", map[string]interface{}{
			"task": t.Name,
			"host": closure.HostContext.Host.Name,
		})
		_ = ReportTaskSkipped(daemonClient, t.Id, t.Name, closure.HostContext.Host.Name, 0)
		ch <- TaskResult{Task: t, Closure: closure, Status: TaskStatusSkipped}
		return ch
	}

	// Determine check mode for this task execution
	checkMode := false
	diffMode := false
	if val, ok := closure.GetFact("ansible_check_mode"); ok {
		if checkModeVal, ok := val.(bool); ok {
			checkMode = checkModeVal
		}
	}
	if val, ok := closure.GetFact("ansible_diff"); ok {
		if diffModeVal, ok := val.(bool); ok {
			diffMode = diffModeVal
		}
	}
	if t.CheckMode != nil {
		checkMode = *t.CheckMode
	}
	if t.Diff != nil {
		diffMode = *t.Diff
	}

	// Create a new closure for this execution with the correct check_mode value.
	// This prevents a task's check_mode from affecting subsequent tasks.
	taskClosure := closure.Clone()
	if taskClosure.ExtraFacts == nil {
		taskClosure.ExtraFacts = make(map[string]interface{})
	}
	taskClosure.ExtraFacts["ansible_check_mode"] = checkMode
	taskClosure.ExtraFacts["ansible_diff"] = diffMode

	// Add task-level vars to the closure
	if t.Vars != nil {
		switch t.Vars.(type) {
		case map[string]interface{}:
			for k, v := range t.Vars.(map[string]interface{}) {
				taskClosure.ExtraFacts[k] = v
			}
		default:
			common.LogWarn("Unsupported task vars type", map[string]interface{}{
				"task": t.Name,
				"host": closure.HostContext.Host.Name,
				"type": fmt.Sprintf("%T", t.Vars),
			})
		}
	}

	// If 'until' is not defined, execute once as normal.
	if t.Until.Expression == "" {
		res := t.executeOnce(taskClosure)
		_ = ReportTaskCompletion(daemonClient, t, res, closure.HostContext.Host.Name, -1)
		ch <- res
		return ch
	}

	// 'until' is defined, so we enter the retry loop.
	retries := t.Retries
	if retries == 0 {
		retries = 3 // Default retries
	}

	delay := t.Delay
	if delay == 0 {
		delay = 5 // Default delay in seconds
	}

	var lastResult TaskResult
	for i := 0; i <= retries; i++ {
		common.LogDebug("Executing task, attempt %d/%d", map[string]interface{}{
			"task":    t.Name,
			"host":    closure.HostContext.Host.Name,
			"attempt": i + 1,
			"retries": retries,
		})

		lastResult = t.executeOnce(taskClosure)

		if t.Register != "" {
			if fact, ok := closure.GetFact(t.Register); ok {
				if factMap, ok := fact.(map[string]interface{}); ok {
					factMap["attempts"] = i + 1
					closure.HostContext.Facts.Store(t.Register, factMap)
				}
			}
		}

		// The variable from the task run must be registered before 'until' is evaluated.
		// `executeOnce` already handles registration via HandleResult.
		// RegisterVariableIfNeeded(lastResult, t, closure)

		// Now evaluate the 'until' condition.
		conditionMet := t.Until.IsEmpty() || t.Until.IsTruthy(taskClosure)

		common.DebugOutput("Evaluated until condition %q -> %t", t.Until, conditionMet)

		if conditionMet {
			// Condition met, task is successful.
			// If the last attempt had an error, we clear it because 'until' is true.
			if lastResult.Error != nil {
				common.LogDebug("Until condition met, ignoring previous error", map[string]interface{}{
					"task":  t.Name,
					"host":  closure.HostContext.Host.Name,
					"error": lastResult.Error.Error(),
				})
				lastResult.Error = nil
				lastResult.Failed = false
				// The status might need to be re-evaluated now that it's not failed.
				if lastResult.Output != nil && lastResult.Output.Changed() {
					lastResult.Status = TaskStatusChanged
				} else {
					lastResult.Status = TaskStatusOk
				}
			}
			lastResult.Duration = time.Since(startTime) // Update total duration

			_ = ReportTaskCompletion(daemonClient, t, lastResult, closure.HostContext.Host.Name, -1)
			ch <- lastResult
			return ch
		}

		// Condition not met, if we have more retries, wait and try again.
		if i < retries {
			common.LogInfo("Until condition not met, waiting for delay before retrying", map[string]interface{}{
				"task":  t.Name,
				"host":  closure.HostContext.Host.Name,
				"delay": delay,
			})
			time.Sleep(time.Duration(delay) * time.Second)
		}
	}

	// All retries are exhausted and the condition was never met. The task fails.
	// The lastResult from the final attempt is returned.
	common.LogWarn("Task failed after all retries, until condition never met", map[string]interface{}{
		"task":    t.Name,
		"host":    closure.HostContext.Host.Name,
		"retries": retries,
	})
	if lastResult.Error == nil {
		lastResult.Error = fmt.Errorf("task failed after %d retries, until condition was never met", retries)
		lastResult.Failed = true
		lastResult.Status = TaskStatusFailed
	}
	lastResult.Duration = time.Since(startTime)

	_ = ReportTaskCompletion(daemonClient, t, lastResult, closure.HostContext.Host.Name, -1)
	ch <- lastResult
	return ch
}

// executeOnce performs a single execution of the task's module.
// It handles templating, execution, and result processing.
func (t Task) executeOnce(closure *Closure) TaskResult {
	startTime := time.Now()
	r := TaskResult{Task: t, Closure: closure, Status: TaskStatusSkipped}

	// The 'when' condition is checked in the calling ExecuteModule function
	// to avoid re-checking in a loop. Here we proceed directly to execution.

	module, ok := GetModule(t.Module)
	if !ok {
		// Try to use the Python fallback module for unknown modules
		common.LogInfo("Module not found in Spage, attempting Python fallback", map[string]interface{}{
			"module": t.Module,
			"host":   closure.HostContext.Host.Name,
		})

		// Check if ansible_python module is available for fallback
		_, pythonOk := GetModule("ansible_python")
		if !pythonOk {
			r.Error = fmt.Errorf("module %s not found and ansible_python fallback module is not available", t.Module)
			return r
		}

		// We can't call the modules package function due to import cycle
		// So we'll return an error and let the user fix the playbook to use ansible_python directly
		r.Error = fmt.Errorf("module %s not found. Consider using the 'ansible_python' module with module_name: %s", t.Module, t.Module)
		return r
	}

	// Evaluate jinja2 in the module input fields
	// Create a copy of the task parameters to avoid modifying the original task
	taskParamsCopy := *t.TaskParams
	if taskParamsCopy.Params.Actual != nil {
		templatedActualProvider, templateErr := TemplateModuleInputFields(taskParamsCopy.Params.Actual, closure)
		if templateErr != nil {
			r.Error = fmt.Errorf("failed to template module input fields for task %s (module %s): %w", t.Name, t.Module, templateErr)
			return r
		}
		taskParamsCopy.Params.Actual = templatedActualProvider // This could be nil if original was nil and TemplateModuleInputFields returns nil
	}

	r.Output, r.Error = module.Execute(taskParamsCopy.Params.Actual, closure, t.BecomeUser)
	duration := time.Since(startTime)
	r.Duration = duration

	res := HandleResult(&r, t, closure)
	return res
}

func (t Task) RevertModule(closure *Closure) chan TaskResult {
	ch := make(chan TaskResult, 1)
	startTime := time.Now()
	r := TaskResult{Task: t, Closure: closure, Status: TaskStatusSkipped}

	shouldExecute, err := t.ShouldExecute(closure)
	if err != nil {
		ch <- TaskResult{Task: t, Closure: closure, Status: TaskStatusFailed, Error: err}
		return ch
	}
	if !shouldExecute {
		common.LogDebug("Skipping revert of task", map[string]interface{}{
			"task": t.Name,
			"host": closure.HostContext.Host.Name,
		})
		ch <- r
		return ch
	}

	// Check if the task's parameters define a revert action.
	if t.TaskParams.Params.Actual == nil || !t.TaskParams.Params.Actual.HasRevert() {
		common.LogDebug("Task has no revert action defined, skipping revert.", map[string]interface{}{
			"task": t.Name,
			"host": closure.HostContext.Host.Name,
		})
		ch <- r
		return ch
	}

	module, ok := GetModule(t.Module)
	if !ok {
		r.Error = fmt.Errorf("module %s not found", t.Module)
		ch <- r
		return ch
	}

	// Load previous output from history using sync.Map.Load
	previousOutputRaw, found := closure.HostContext.History.Load(t.Name) // Changed from index access
	if !found {
		common.LogWarn("No previous history found for task during revert", map[string]interface{}{
			"task": t.Name,
			"host": closure.HostContext.Host.Name,
		})
		previousOutputRaw = nil
	}

	// Type assert the loaded value to ModuleOutput
	var previousOutput ModuleOutput
	if previousOutputRaw != nil {
		var assertOk bool
		previousOutput, assertOk = previousOutputRaw.(ModuleOutput)
		if !assertOk {
			// If the assertion fails, it might be because the type information was lost during serialization (e.g., in Temporal).
			// We can check if the raw type is a map and wrap it in a generic output type.
			if asMap, ok := previousOutputRaw.(map[string]interface{}); ok {
				previousOutput = GenericMapOutput(asMap)
				assertOk = true
			}
		}

		if !assertOk {
			r.Error = fmt.Errorf("failed to assert previous history type (%T) to ModuleOutput for task %s", previousOutputRaw, t.Name)
			r.Duration = time.Since(startTime)
			ch <- r
			return ch
		}
	}

	// Evaluate jinja2 in the module input fields
	if t.TaskParams.Params.Actual != nil {
		templatedActualProvider, templateErr := TemplateModuleInputFields(t.TaskParams.Params.Actual, closure)
		if templateErr != nil {
			r.Error = fmt.Errorf("failed to template module input fields for task %s (module %s): %w", t.Name, t.Module, templateErr)
			ch <- r
			return ch
		}
		t.TaskParams.Params.Actual = templatedActualProvider // This could be nil if original was nil and TemplateModuleInputFields returns nil
	}

	// Convert GenericMapOutput back to the expected concrete type if needed
	convertedPreviousOutput, err := convertGenericOutputToConcrete(previousOutput, module)
	if err != nil {
		r.Error = fmt.Errorf("failed to convert previous output for revert: %w", err)
		r.Duration = time.Since(startTime)
		ch <- r
		return ch
	}

	// Pass t.Params.Actual to module.Revert
	// Similar nil check considerations as in ExecuteModule for t.Params.Actual
	r.Output, r.Error = module.Revert(t.TaskParams.Params.Actual, closure, convertedPreviousOutput, t.BecomeUser) // Pass potentially nil previousOutput
	duration := time.Since(startTime)
	r.Duration = duration

	res := HandleResult(&r, t, closure)
	ch <- res
	return ch
}

// formatConditionsForError returns a string representation of conditions for error messages
func formatConditionsForError(conditions interface{}) string {
	switch v := conditions.(type) {
	case string:
		return v
	case []interface{}:
		var condStrs []string
		for _, condition := range v {
			if condStr, ok := condition.(string); ok {
				condStrs = append(condStrs, fmt.Sprintf("'%s'", condStr))
			} else {
				condStrs = append(condStrs, fmt.Sprintf("%v", condition))
			}
		}
		return fmt.Sprintf("[%s]", strings.Join(condStrs, ", "))
	default:
		return fmt.Sprintf("%v", conditions)
	}
}

func HandleResult(r *TaskResult, t Task, c *Closure) TaskResult {
	r.Task = t
	r.Closure = c

	if r.Error != nil {
		r.Status = TaskStatusFailed
		r.Failed = true
	} else if r.Output.Changed() {
		r.Status = TaskStatusChanged
		r.Changed = true
	} else {
		r.Status = TaskStatusOk
	}
	RegisterVariableIfNeeded(*r, t, c)

	// Evaluate failed_when only if the module execution itself succeeded
	if r.Error == nil && !t.FailedWhen.IsEmpty() {
		if !t.FailedWhen.IsTruthy(c) {
			// Condition is false, do nothing
		} else {
			r.Error = fmt.Errorf("failed_when condition '%s' evaluated to true", formatConditionsForError(t.FailedWhen))
			r.Status = TaskStatusFailed
			r.Failed = true
			common.DebugOutput("Evaluated failed_when condition %s: %t",
				formatConditionsForError(t.FailedWhen), true)
		}
	}

	if r.Error != nil && !t.IgnoreErrors.IsEmpty() && t.IgnoreErrors.IsTruthy(c) {
		common.LogWarn("Task failed but error ignored due to ignore_errors=true", map[string]interface{}{
			"task":  t.Name,
			"host":  c.HostContext.Host.Name,
			"error": r.Error.Error(),
		})
		// Wrap the original error in IgnoredTaskError
		r.Error = &IgnoredTaskError{OriginalErr: r.Error}
		r.Failed = true
	}

	if r.Status != TaskStatusFailed {
		if !t.ChangedWhen.IsEmpty() {
			// Only override the module's Changed() result when changed_when is explicitly set
			if t.ChangedWhen.IsTruthy(c) {
				r.Status = TaskStatusChanged
				r.Changed = true
			} else {
				r.Status = TaskStatusOk
				r.Changed = false
			}
		}
		// If changed_when is empty, keep the module's original Changed() result
	}

	// Notify handlers if the task changed and has notify field
	if len(t.Notify) > 0 {
		common.LogDebug("Evaluating handler notification", map[string]interface{}{
			"task":                   t.Name,
			"host":                   c.HostContext.Host.Name,
			"handlers":               t.Notify,
			"changed":                r.Changed,
			"failed":                 r.Failed,
			"handler_tracker_exists": c.HostContext.HandlerTracker != nil,
		})
		if r.Changed && !r.Failed {
			if c.HostContext.HandlerTracker != nil {
				c.HostContext.HandlerTracker.NotifyHandlers(t.Notify)
				common.LogDebug("Notified handlers", map[string]interface{}{
					"task":     t.Name,
					"host":     c.HostContext.Host.Name,
					"handlers": t.Notify,
					"changed":  r.Changed,
				})
			}
		}
	}

	// failed_when/changed_when might depend on results of this task, so we need to evaluate them after registration
	// However, we should update the changed/failed status after evaluating these conditions.
	setTaskStatus(*r, t, c)
	return *r
}
func RegisterVariableIfNeeded(result TaskResult, task Task, c *Closure) {
	// Only register if the task has a name assigned to the 'register' key
	if task.Register == "" {
		return
	}

	var valueToStore interface{}
	var ignoredErr *IgnoredTaskError

	// Check if the error is an IgnoredTaskError or just a regular error
	if errors.As(result.Error, &ignoredErr) {
		// It's an ignored error
		originalErr := ignoredErr.Unwrap()    // Get the original error
		failureMap := map[string]interface{}{ // Register failure details
			"failed":  true,
			"changed": false,
			"msg":     originalErr.Error(),
			"ignored": true, // Add an explicit ignored flag
		}
		// Include output facts if available
		if result.Output != nil {
			if factProvider, ok := result.Output.(FactProvider); ok {
				outputFacts := factProvider.AsFacts()
				for k, v := range outputFacts {
					failureMap[k] = v
				}
			}
		}
		valueToStore = failureMap
		common.LogDebug("Ignored error", map[string]interface{}{
			"task":  task.Name,
			"host":  c.HostContext.Host.Name,
			"error": originalErr.Error(),
			"value": valueToStore,
		})
	} else if result.Error != nil {
		// It's a regular, non-ignored error
		failureMap := map[string]interface{}{ // Register failure details
			"failed":  true,
			"changed": false,
			"msg":     result.Error.Error(),
		}
		// Include output facts if available
		if result.Output != nil {
			if factProvider, ok := result.Output.(FactProvider); ok {
				outputFacts := factProvider.AsFacts()
				for k, v := range outputFacts {
					failureMap[k] = v
				}
			}
		}
		valueToStore = failureMap
	} else if result.Output != nil {
		// If successful and output exists, register the output facts
		valueToStore = ConvertOutputToFactsMap(result.Output)
	} else {
		// If successful but no output (e.g., skipped task), register a minimal success map
		valueToStore = map[string]interface{}{ // Ensure something is registered for skipped/ok tasks
			"failed":  false,
			"changed": false,
			"skipped": result.Output == nil, // Mark as skipped if output is nil
			"ignored": false,                // Explicitly false for non-ignored cases
		}
	}

	if valueToStore != nil {
		common.LogDebug("Registering variable", map[string]interface{}{
			"task":     task.Name,
			"host":     c.HostContext.Host.Name,
			"variable": task.Register,
			"value":    valueToStore, // Log the actual map being stored
		})
		c.HostContext.Facts.Store(task.Register, valueToStore)
	}
}

func setTaskStatus(result TaskResult, task Task, c *Closure) {
	if task.Register == "" {
		return
	}
	facts, _ := c.GetFact(task.Register)
	if facts == nil {
		facts = map[string]interface{}{
			"failed":  result.Failed,
			"changed": result.Changed,
		}
	} else {
		if factsMap, ok := facts.(map[string]interface{}); ok {
			factsMap["failed"] = result.Failed
			factsMap["changed"] = result.Changed
			facts = factsMap // Assign back the modified map
		} else {
			// Handle cases where the loaded value is not a map[string]interface{}
			// For now, let's overwrite with a new map, but you might want different logic.
			facts = map[string]interface{}{
				"failed":  result.Failed,
				"changed": result.Changed,
			}
		}
	}
	c.HostContext.Facts.Store(task.Register, facts)
}

// convertGenericOutputToConcrete converts GenericMapOutput back to the expected concrete output type
// for a given module. This is used in the Revert method to handle cases where output was serialized/deserialized.
func convertGenericOutputToConcrete(output ModuleOutput, module Module) (ModuleOutput, error) {
	if output == nil {
		return nil, nil
	}

	// If it's already the correct concrete type, return as-is
	if _, ok := output.(GenericMapOutput); !ok {
		return output, nil
	}

	// Convert from GenericMapOutput to the expected concrete type
	genericOutput, ok := output.(GenericMapOutput)
	if !ok {
		return output, nil // This shouldn't happen based on the check above, but be safe
	}

	// Get the expected output type from the module
	expectedType := module.OutputType()

	// Create a new instance of the expected type
	concreteOutputPtr := reflect.New(expectedType)
	concreteOutputValue := concreteOutputPtr.Elem()

	// Populate fields from the generic map using reflection
	err := populateStructFromMap(concreteOutputValue, genericOutput)
	if err != nil {
		return nil, fmt.Errorf("failed to populate %s from GenericMapOutput: %w", expectedType.Name(), err)
	}

	// Return the concrete instance
	concreteOutput := concreteOutputValue.Interface()
	if moduleOutput, ok := concreteOutput.(ModuleOutput); ok {
		return moduleOutput, nil
	}

	return nil, fmt.Errorf("converted type %T does not implement ModuleOutput interface", concreteOutput)
}

// populateStructFromMap uses reflection to populate struct fields from a map[string]interface{}
func populateStructFromMap(structValue reflect.Value, data map[string]interface{}) error {
	structType := structValue.Type()

	for i := 0; i < structValue.NumField(); i++ {
		fieldValue := structValue.Field(i)
		fieldType := structType.Field(i)

		// Skip unexported fields
		if !fieldValue.CanSet() {
			continue
		}

		// Skip embedded ModuleOutput interface
		if fieldType.Type.String() == "pkg.ModuleOutput" {
			continue
		}

		// Get the map key (use lowercase field name)
		mapKey := strings.ToLower(fieldType.Name)
		mapValue, exists := data[mapKey]
		if !exists {
			continue
		}

		err := setFieldValue(fieldValue, mapValue)
		if err != nil {
			return fmt.Errorf("failed to set field %s: %w", fieldType.Name, err)
		}
	}

	return nil
}

// setFieldValue sets a reflect.Value from an interface{} value, handling type conversions
func setFieldValue(fieldValue reflect.Value, mapValue interface{}) error {
	if mapValue == nil {
		return nil
	}

	fieldType := fieldValue.Type()
	mapValueType := reflect.TypeOf(mapValue)

	// Handle RevertableChange[T] types specially
	if fieldType.Name() != "" && strings.HasPrefix(fieldType.String(), "pkg.RevertableChange[") {
		return setRevertableChangeField(fieldValue, mapValue)
	}

	// Direct assignment if types match
	if mapValueType.AssignableTo(fieldType) {
		fieldValue.Set(reflect.ValueOf(mapValue))
		return nil
	}

	// Handle type conversions
	mapValueReflect := reflect.ValueOf(mapValue)
	if mapValueReflect.Type().ConvertibleTo(fieldType) {
		fieldValue.Set(mapValueReflect.Convert(fieldType))
		return nil
	}

	// Handle slice conversions (e.g., []interface{} to []string)
	if mapValueReflect.Kind() == reflect.Slice && fieldType.Kind() == reflect.Slice {
		return convertSlice(fieldValue, mapValueReflect, fieldType)
	}

	// Handle string to other basic types
	if mapValueReflect.Kind() == reflect.String && fieldType.Kind() != reflect.String {
		// For simplicity, only handle basic string conversion cases
		// More complex cases could be added as needed
		return fmt.Errorf("cannot convert string %q to %s", mapValue, fieldType)
	}

	return fmt.Errorf("cannot assign %T to %s", mapValue, fieldType)
}

// convertSlice converts between slice types, e.g., []interface{} to []string
func convertSlice(fieldValue reflect.Value, sourceSlice reflect.Value, targetType reflect.Type) error {
	sourceLen := sourceSlice.Len()
	targetElementType := targetType.Elem()

	// Create a new slice of the target type
	newSlice := reflect.MakeSlice(targetType, sourceLen, sourceLen)

	for i := 0; i < sourceLen; i++ {
		sourceElement := sourceSlice.Index(i)
		targetElement := newSlice.Index(i)

		// Try to convert each element
		sourceElementValue := sourceElement.Interface()
		sourceElementReflect := reflect.ValueOf(sourceElementValue)

		// Direct assignment if types match
		if sourceElementReflect.Type().AssignableTo(targetElementType) {
			targetElement.Set(sourceElementReflect)
		} else if sourceElementReflect.Type().ConvertibleTo(targetElementType) {
			targetElement.Set(sourceElementReflect.Convert(targetElementType))
		} else {
			return fmt.Errorf("cannot convert slice element %T to %s", sourceElementValue, targetElementType)
		}
	}

	fieldValue.Set(newSlice)
	return nil
}

// setRevertableChangeField specifically handles RevertableChange[T] field population
func setRevertableChangeField(fieldValue reflect.Value, mapValue interface{}) error {
	changeMap, ok := mapValue.(map[string]interface{})
	if !ok {
		return fmt.Errorf("expected map[string]interface{} for RevertableChange, got %T", mapValue)
	}

	// Create a new RevertableChange instance
	changeValue := reflect.New(fieldValue.Type()).Elem()

	// Set Before field
	if beforeVal, exists := changeMap["before"]; exists {
		beforeField := changeValue.FieldByName("Before")
		if beforeField.IsValid() && beforeField.CanSet() {
			err := setFieldValue(beforeField, beforeVal)
			if err != nil {
				return fmt.Errorf("failed to set Before field: %w", err)
			}
		}
	}

	// Set After field
	if afterVal, exists := changeMap["after"]; exists {
		afterField := changeValue.FieldByName("After")
		if afterField.IsValid() && afterField.CanSet() {
			err := setFieldValue(afterField, afterVal)
			if err != nil {
				return fmt.Errorf("failed to set After field: %w", err)
			}
		}
	}

	fieldValue.Set(changeValue)
	return nil
}

// GenericMapOutput provides a generic, map-based implementation of ModuleOutput.
// This is useful for reconstructing ModuleOutput from history where type information may have been lost.
type GenericMapOutput map[string]interface{}

// Changed checks for a "changed" key in the map.
func (g GenericMapOutput) Changed() bool {
	if changed, ok := g["changed"].(bool); ok {
		return changed
	}
	return false
}

// String provides a simple string representation of the map.
func (g GenericMapOutput) String() string {
	return fmt.Sprintf("%v", map[string]interface{}(g))
}

var moduleOutputType = reflect.TypeOf((*ModuleOutput)(nil)).Elem()

func ConvertOutputToFactsMap(output ModuleOutput) interface{} {
	if output == nil {
		return nil
	}

	// If the output implements FactProvider, use its AsFacts method.
	// It means that the module has custom output requirements that it manages by itself.
	if factProvider, ok := output.(FactProvider); ok {
		return factProvider.AsFacts()
	}

	outputValue := reflect.ValueOf(output)
	outputType := outputValue.Type()

	if outputType.Kind() == reflect.Ptr {
		if outputValue.IsNil() {
			return nil
		}
		outputValue = outputValue.Elem()
		outputType = outputValue.Type()
	}

	if outputValue.IsValid() && outputType.Kind() == reflect.Struct {
		factsMap := make(map[string]interface{})
		for i := 0; i < outputValue.NumField(); i++ {
			fieldValue := outputValue.Field(i)
			typeField := outputType.Field(i)

			if typeField.IsExported() {
				if typeField.Type == moduleOutputType {
					continue
				}
				key := strings.ToLower(typeField.Name)
				fieldInterface := fieldValue.Interface()
				fieldValType := reflect.TypeOf(fieldInterface)
				fieldValKind := fieldValType.Kind()
				if fieldValKind == reflect.Ptr {
					fieldValKind = fieldValType.Elem().Kind()
				}

				if fieldValKind == reflect.Struct {
					factsMap[key] = convertInterfaceToMapRecursive(fieldInterface)
				} else {
					factsMap[key] = fieldInterface
				}
			}
		}
		if changedMethod := outputValue.MethodByName("Changed"); changedMethod.IsValid() {
			results := changedMethod.Call(nil)
			if len(results) > 0 && results[0].Kind() == reflect.Bool {
				factsMap["changed"] = results[0].Bool()
			}
		} else if outputValue.CanAddr() {
			addrValue := outputValue.Addr()
			if changedMethod := addrValue.MethodByName("Changed"); changedMethod.IsValid() {
				results := changedMethod.Call(nil)
				if len(results) > 0 && results[0].Kind() == reflect.Bool {
					factsMap["changed"] = results[0].Bool()
				}
			}
		}
		return factsMap
	}
	return output
}

func convertInterfaceToMapRecursive(data interface{}) interface{} {
	if data == nil {
		return nil
	}

	value := reflect.ValueOf(data)
	typeInfo := value.Type()

	if typeInfo.Kind() == reflect.Ptr {
		if value.IsNil() {
			return nil
		}
		value = value.Elem()
		typeInfo = value.Type()
	}

	if typeInfo.Kind() != reflect.Struct {
		return data
	}

	mapResult := make(map[string]interface{})
	for i := 0; i < value.NumField(); i++ {
		fieldValue := value.Field(i)
		typeField := typeInfo.Field(i)

		if typeField.IsExported() {
			key := strings.ToLower(typeField.Name)
			mapResult[key] = convertInterfaceToMapRecursive(fieldValue.Interface())
		}
	}
	return mapResult
}
