// Package cron provides a scheduler for autonomous bot activity.
// It is part of the impure shell: it reads files, calls LLMs, and sends messages
// over the bot's transport (unibus).
package cron

import (
	"context"
	"log/slog"

	"github.com/robfig/cron/v3"

	"github.com/enmanuel/agents/internal/config"
	coretypes "github.com/enmanuel/agents/pkg/llm"
)

// Sender is the subset of the bot's transport sender needed by the scheduler.
type Sender interface {
	SendMarkdown(ctx context.Context, roomID, markdown string) error
}

// Scheduler fires configured schedules and executes send_message or llm_prompt actions.
type Scheduler struct {
	cfg    []config.ScheduleCfg
	sender Sender
	llm    coretypes.CompleteFunc // nil when agent has no LLM
	model  string
	logger *slog.Logger
	cron   *cron.Cron
}

// New creates a Scheduler. llm and model are optional (nil/empty for agents without LLM).
func New(
	cfg []config.ScheduleCfg,
	sender Sender,
	llm coretypes.CompleteFunc,
	model string,
	logger *slog.Logger,
) *Scheduler {
	return &Scheduler{
		cfg:    cfg,
		sender: sender,
		llm:    llm,
		model:  model,
		logger: logger.With("component", "cron"),
		cron:   cron.New(),
	}
}

// Fire immediately executes the action for the given schedule, bypassing the cron timer.
// Useful for tests and manual triggering from CLI.
func (s *Scheduler) Fire(ctx context.Context, sc config.ScheduleCfg) {
	room := sc.OutputRoom
	if room == "" {
		s.logger.Warn("Fire: schedule has no output_room, skipping", "name", sc.Name)
		return
	}
	handler := s.buildHandler(sc)
	if handler == nil {
		s.logger.Warn("Fire: unsupported action kind", "name", sc.Name, "kind", sc.Action.Kind)
		return
	}
	handler(ctx, room)
}

// Start registers all schedules and starts the cron loop.
// It returns when ctx is cancelled, stopping the cron runner.
func (s *Scheduler) Start(ctx context.Context) {
	for _, sc := range s.cfg {
		sc := sc // capture range var
		if sc.Cron == "" || sc.Action.Kind == "" {
			s.logger.Warn("skipping invalid schedule", "name", sc.Name, "cron", sc.Cron, "kind", sc.Action.Kind)
			continue
		}

		room := sc.OutputRoom
		if room == "" {
			s.logger.Warn("schedule has no output_room, skipping", "name", sc.Name)
			continue
		}

		handler := s.buildHandler(sc)
		if handler == nil {
			s.logger.Warn("unsupported action kind, skipping", "name", sc.Name, "kind", sc.Action.Kind)
			continue
		}

		_, err := s.cron.AddFunc(sc.Cron, func() {
			handler(ctx, room)
		})
		if err != nil {
			s.logger.Error("failed to register schedule",
				"name", sc.Name,
				"cron", sc.Cron,
				"err", err,
			)
			continue
		}

		s.logger.Info("schedule registered", "name", sc.Name, "cron", sc.Cron, "kind", sc.Action.Kind, "room", room)
	}

	s.cron.Start()
	s.logger.Info("cron scheduler started", "schedules", len(s.cfg))

	<-ctx.Done()
	s.logger.Info("cron scheduler stopping")
	cronCtx := s.cron.Stop()
	<-cronCtx.Done()
	s.logger.Info("cron scheduler stopped")
}
