package handlers

import (
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/robfig/cron/v3"

	"switchboard-core/models"
	"switchboard-core/store"
	"switchboard-core/triggers"
)

// ScheduleHandler provides CRUD for user-created scheduled tasks.
type ScheduleHandler struct {
	stores store.Stores
	engine *triggers.Engine
}

// NewScheduleHandler creates a ScheduleHandler.
func NewScheduleHandler(stores store.Stores, engine *triggers.Engine) *ScheduleHandler {
	return &ScheduleHandler{stores: stores, engine: engine}
}

// ── User Endpoints ───────────────────────────

// ListMySchedules returns schedules created by the current user.
// GET /api/v1/schedules
func (h *ScheduleHandler) ListMySchedules(c *gin.Context) {
	userID := c.GetString("user_id")
	tasks, err := h.stores.ScheduledTasks.ListByCreator(c.Request.Context(), userID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"schedules": tasks})
}

// CreateSchedule creates a new scheduled task.
// POST /api/v1/schedules
func (h *ScheduleHandler) CreateSchedule(c *gin.Context) {
	var req struct {
		Name           string `json:"name" binding:"required"`
		Description    string `json:"description"`
		CronExpr       string `json:"cron_expr" binding:"required"`
		Script         string `json:"script"`
		TemplateID     string `json:"template_id"`
		TemplateParams any    `json:"template_params"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Validate cron expression
	parser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
	schedule, err := parser.Parse(req.CronExpr)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid cron expression: " + err.Error()})
		return
	}

	nextFire := schedule.Next(time.Now())
	userID := c.GetString("user_id")

	task := &models.ScheduledTask{
		Name:        req.Name,
		Description: req.Description,
		CreatorID:   userID,
		RunAs:       models.RunAsCreator,
		CronExpr:    req.CronExpr,
		NextFireAt:  &nextFire,
		Enabled:     true,
		Script:      req.Script,
		TemplateID:  req.TemplateID,
	}

	if err := h.stores.ScheduledTasks.Create(c.Request.Context(), task); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	// Register with engine
	h.engine.RegisterSchedule(task)

	c.JSON(http.StatusCreated, task)
}

// GetSchedule returns a single scheduled task.
// GET /api/v1/schedules/:id
func (h *ScheduleHandler) GetSchedule(c *gin.Context) {
	task, err := h.stores.ScheduledTasks.GetByID(c.Request.Context(), c.Param("id"))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	if task == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "schedule not found"})
		return
	}

	// Check ownership (non-admin can only see own schedules)
	userID := c.GetString("user_id")
	if task.CreatorID != userID {
		c.JSON(http.StatusNotFound, gin.H{"error": "schedule not found"})
		return
	}

	c.JSON(http.StatusOK, task)
}

// UpdateSchedule updates a scheduled task.
// PUT /api/v1/schedules/:id
func (h *ScheduleHandler) UpdateSchedule(c *gin.Context) {
	task, err := h.stores.ScheduledTasks.GetByID(c.Request.Context(), c.Param("id"))
	if err != nil || task == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "schedule not found"})
		return
	}

	userID := c.GetString("user_id")
	if task.CreatorID != userID {
		c.JSON(http.StatusNotFound, gin.H{"error": "schedule not found"})
		return
	}

	var req struct {
		Name        *string `json:"name"`
		Description *string `json:"description"`
		CronExpr    *string `json:"cron_expr"`
		Script      *string `json:"script"`
		Enabled     *bool   `json:"enabled"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	if req.Name != nil {
		task.Name = *req.Name
	}
	if req.Description != nil {
		task.Description = *req.Description
	}
	if req.Script != nil {
		task.Script = *req.Script
	}
	if req.Enabled != nil {
		task.Enabled = *req.Enabled
	}
	if req.CronExpr != nil {
		parser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
		schedule, err := parser.Parse(*req.CronExpr)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "invalid cron expression: " + err.Error()})
			return
		}
		task.CronExpr = *req.CronExpr
		nextFire := schedule.Next(time.Now())
		task.NextFireAt = &nextFire
	}

	if err := h.stores.ScheduledTasks.Update(c.Request.Context(), task); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	// Re-register with engine
	h.engine.UnregisterSchedule(task.ID)
	if task.Enabled {
		h.engine.RegisterSchedule(task)
	}

	c.JSON(http.StatusOK, task)
}

// DeleteSchedule deletes a scheduled task.
// DELETE /api/v1/schedules/:id
func (h *ScheduleHandler) DeleteSchedule(c *gin.Context) {
	task, err := h.stores.ScheduledTasks.GetByID(c.Request.Context(), c.Param("id"))
	if err != nil || task == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "schedule not found"})
		return
	}

	userID := c.GetString("user_id")
	if task.CreatorID != userID {
		c.JSON(http.StatusNotFound, gin.H{"error": "schedule not found"})
		return
	}

	h.engine.UnregisterSchedule(task.ID)
	if err := h.stores.ScheduledTasks.Delete(c.Request.Context(), task.ID); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{"ok": true})
}

// RunSchedule manually triggers a scheduled task (test run).
// POST /api/v1/schedules/:id/run
func (h *ScheduleHandler) RunSchedule(c *gin.Context) {
	task, err := h.stores.ScheduledTasks.GetByID(c.Request.Context(), c.Param("id"))
	if err != nil || task == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "schedule not found"})
		return
	}

	userID := c.GetString("user_id")
	if task.CreatorID != userID {
		c.JSON(http.StatusNotFound, gin.H{"error": "schedule not found"})
		return
	}

	// Fire immediately in a goroutine
	go h.engine.ManualRun(task)

	c.JSON(http.StatusAccepted, gin.H{"ok": true, "message": "task queued for execution"})
}

// ListScheduleLogs returns execution logs for a scheduled task.
// GET /api/v1/schedules/:id/logs
func (h *ScheduleHandler) ListScheduleLogs(c *gin.Context) {
	task, err := h.stores.ScheduledTasks.GetByID(c.Request.Context(), c.Param("id"))
	if err != nil || task == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "schedule not found"})
		return
	}

	userID := c.GetString("user_id")
	if task.CreatorID != userID {
		c.JSON(http.StatusNotFound, gin.H{"error": "schedule not found"})
		return
	}

	limit := parseIntParam(c, "limit", 50)
	logs, err := h.stores.Triggers.ListLogs(c.Request.Context(), task.ID, limit)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"logs": logs})
}

// ── Admin Endpoints ──────────────────────────

// AdminListSchedules returns all scheduled tasks (admin view).
// GET /admin/schedules
func (h *ScheduleHandler) AdminListSchedules(c *gin.Context) {
	opts := store.ScheduledTaskListOptions{
		ListOptions: store.ListOptions{
			Limit:  parseIntParam(c, "limit", 50),
			Offset: parseIntParam(c, "offset", 0),
		},
		CreatorID: c.Query("creator_id"),
	}
	if e := c.Query("enabled"); e != "" {
		b := e == "true"
		opts.Enabled = &b
	}

	tasks, total, err := h.stores.ScheduledTasks.List(c.Request.Context(), opts)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"schedules": tasks, "total": total})
}

// AdminEnableSchedule enables a scheduled task.
// PUT /admin/schedules/:id/enable
func (h *ScheduleHandler) AdminEnableSchedule(c *gin.Context) {
	id := c.Param("id")
	if err := h.stores.ScheduledTasks.SetEnabled(c.Request.Context(), id, true); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	task, _ := h.stores.ScheduledTasks.GetByID(c.Request.Context(), id)
	if task != nil {
		h.engine.RegisterSchedule(task)
	}
	c.JSON(http.StatusOK, gin.H{"ok": true})
}

// AdminDisableSchedule disables a scheduled task.
// PUT /admin/schedules/:id/disable
func (h *ScheduleHandler) AdminDisableSchedule(c *gin.Context) {
	id := c.Param("id")
	h.engine.UnregisterSchedule(id)
	if err := h.stores.ScheduledTasks.SetEnabled(c.Request.Context(), id, false); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"ok": true})
}

// AdminDeleteSchedule deletes a scheduled task.
// DELETE /admin/schedules/:id
func (h *ScheduleHandler) AdminDeleteSchedule(c *gin.Context) {
	id := c.Param("id")
	h.engine.UnregisterSchedule(id)
	if err := h.stores.ScheduledTasks.Delete(c.Request.Context(), id); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"ok": true})
}
