package triggers

import (
	"context"
	"log"
	"time"

	"github.com/robfig/cron/v3"
	"go.starlark.net/starlark"

	starlarkjson "go.starlark.net/lib/json"

	"switchboard-core/models"
	"switchboard-core/sandbox"
)

// wireScheduledTask registers a cron entry for a scheduled task.
func (e *Engine) wireScheduledTask(t *models.ScheduledTask) {
	taskID := t.ID
	creatorID := t.CreatorID
	runAs := t.RunAs
	script := t.Script
	cronExpr := t.CronExpr

	// Parse cron with standard 5-field format (minute hour dom month dow)
	parser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
	schedule, err := parser.Parse(cronExpr)
	if err != nil {
		log.Printf("  ⚠️  schedule[%s]: invalid cron %q: %v", taskID, cronExpr, err)
		return
	}

	// Compute next fire time
	now := time.Now()
	nextFire := schedule.Next(now)
	_ = e.stores.ScheduledTasks.UpdateFireState(context.Background(), taskID,
		time.Time{}, &nextFire, "", 0)

	entryID := e.cron.Schedule(schedule, cron.FuncJob(func() {
		e.fireScheduledTask(taskID, creatorID, runAs, script, cronExpr)
	}))

	e.mu.Lock()
	e.cronIDs[taskID] = entryID
	e.mu.Unlock()
}

// fireScheduledTask invokes a scheduled task's Starlark script in a restricted sandbox.
func (e *Engine) fireScheduledTask(taskID, creatorID, runAs, script, cronExpr string) {
	start := time.Now()
	ctx := e.ctx
	if ctx == nil {
		ctx = context.Background()
	}

	// Re-check task is still enabled
	task, err := e.stores.ScheduledTasks.GetByID(ctx, taskID)
	if err != nil || task == nil || !task.Enabled {
		return
	}

	// Check creator is still active (unless run_as=system)
	if runAs == models.RunAsCreator {
		user, err := e.stores.Users.GetByID(ctx, creatorID)
		if err != nil || user == nil || !user.IsActive {
			log.Printf("  ⚠️  schedule[%s]: creator %s inactive, pausing", taskID, creatorID)
			_ = e.stores.ScheduledTasks.SetEnabled(ctx, taskID, false)
			return
		}
	}

	// Build restricted module set
	modules := e.buildRestrictedModules(ctx, creatorID, runAs)

	// Execute script in sandbox
	sb := sandbox.New(sandbox.DefaultConfig())
	result, execErr := sb.ExecWithLoader(ctx, "schedule/"+taskID+".star", script, modules, nil)

	var output string
	if result != nil {
		output = result.Output
	}

	// Call main() if defined
	if execErr == nil && result != nil {
		if mainFn, ok := result.Globals["main"]; ok {
			if callable, ok := mainFn.(starlark.Callable); ok {
				// Build context dict
				ctxDict := starlark.NewDict(4)
				_ = ctxDict.SetKey(starlark.String("trigger_type"), starlark.String("schedule"))
				_ = ctxDict.SetKey(starlark.String("task_id"), starlark.String(taskID))
				_ = ctxDict.SetKey(starlark.String("cron_expr"), starlark.String(cronExpr))
				_ = ctxDict.SetKey(starlark.String("fired_at"), starlark.String(start.UTC().Format(time.RFC3339)))

				_, callOutput, callErr := sb.Call(ctx, callable, starlark.Tuple{ctxDict}, nil)
				output += callOutput
				if callErr != nil {
					execErr = callErr
				}
			}
		}
	}

	duration := int(time.Since(start).Milliseconds())
	errStr := ""
	if execErr != nil {
		errStr = execErr.Error()
		log.Printf("  ⚠️  schedule[%s] error: %v", taskID, execErr)
	}

	// Compute next fire time
	parser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
	if sched, err := parser.Parse(cronExpr); err == nil {
		nextFire := sched.Next(time.Now())
		_ = e.stores.ScheduledTasks.UpdateFireState(ctx, taskID, start, &nextFire, errStr, duration)
	} else {
		_ = e.stores.ScheduledTasks.UpdateFireState(ctx, taskID, start, nil, errStr, duration)
	}

	// Log execution
	e.logExecution("", taskID, start, duration, execErr == nil, errStr, output)
	e.publishEvent("trigger.fired", taskID)
}

// buildRestrictedModules creates the limited module set for scheduled tasks.
// Scheduled scripts can: read settings, load libraries, use JSON, resolve connections.
// They CANNOT: create DB tables, make raw HTTP calls, read secrets.
func (e *Engine) buildRestrictedModules(ctx context.Context, creatorID, runAs string) map[string]starlark.Value {
	modules := make(map[string]starlark.Value)

	// JSON is always available
	modules["json"] = starlarkjson.Module

	// Settings module — read-only access to platform settings
	// (no package context for scheduled tasks; they read global settings)
	if e.stores.Packages != nil {
		modules["settings"] = sandbox.BuildSettingsModule(ctx, e.stores, "", creatorID, "")
	}

	// Notifications module — if available
	if e.runner != nil {
		// The runner has the notifier attached; we need to access it.
		// For now, scheduled tasks don't get the notifications module
		// since it requires a package context. This can be enhanced later.
	}

	// Connections module — read-only resolve for HTTP via existing connections
	// This allows scheduled tasks to make HTTP calls through configured connections.
	// The RunContext carries the creator's user ID for connection resolution.

	return modules
}

// RunContext builds a sandbox.RunContext for a scheduled task invocation.
func scheduleRunContext(creatorID, runAs string) *sandbox.RunContext {
	if runAs == models.RunAsSystem {
		return nil // system context — no user scoping
	}
	return &sandbox.RunContext{
		UserID: creatorID,
	}
}
