package api

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/mrhid6/vantage/server/internal/auth"
	"github.com/mrhid6/vantage/server/internal/models"
	"github.com/mrhid6/vantage/server/internal/services"
)

func registerWorkflowRoutes(g *gin.RouterGroup) {
	g.GET("/steps", listSteps)
	g.POST("/steps", createStep)
	g.PUT("/steps/:id", updateStep)
	g.DELETE("/steps/:id", deleteStep)
	g.GET("/steps/:id/export", exportStep)
	g.POST("/steps/import", importStep)
	g.POST("/steps/seed-defaults", seedDefaults)
	g.GET("/steps/usage", stepUsage)
	g.POST("/steps/parse", parseStep)

	g.GET("/workflows", listWorkflows)
	g.POST("/workflows", createWorkflow)
	g.GET("/workflows/:id", getWorkflow)
	g.PUT("/workflows/:id", updateWorkflow)
	g.DELETE("/workflows/:id", deleteWorkflow)
	g.POST("/workflows/:id/run", runWorkflow)
	g.GET("/workflows/:id/runs", listWorkflowRuns)

	g.GET("/runs/:runId", getRun)
	g.POST("/runs/:runId/cancel", cancelRun)
	g.GET("/runs/:runId/servers/:serverId/logs", getServerRunLog)
	g.GET("/runs/:runId/servers/:serverId/logs/stream", streamServerRunLog)
}

var uuidLike = regexp.MustCompile(`^[a-zA-Z0-9-]{1,64}$`)

func getServerRunLog(c *gin.Context) {
	runID, serverID := c.Param("runId"), c.Param("serverId")
	if !uuidLike.MatchString(runID) || !uuidLike.MatchString(serverID) {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
		return
	}
	path := services.ServerRunLogPath(runID, serverID)
	b, err := os.ReadFile(path)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "no logs"})
		return
	}
	c.Data(http.StatusOK, "text/plain; charset=utf-8", b)
}

func streamServerRunLog(c *gin.Context) {
	runID, serverID := c.Param("runId"), c.Param("serverId")
	if !uuidLike.MatchString(runID) || !uuidLike.MatchString(serverID) {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
		return
	}
	path := services.ServerRunLogPath(runID, serverID)

	c.Writer.Header().Set("Content-Type", "text/event-stream")
	c.Writer.Header().Set("Cache-Control", "no-cache")
	c.Writer.Header().Set("Connection", "keep-alive")
	c.Writer.Header().Set("X-Accel-Buffering", "no")

	flusher, ok := c.Writer.(http.Flusher)
	if !ok {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "stream unsupported"})
		return
	}

	var offset int64
	sendNew := func() {
		f, err := os.Open(path)
		if err != nil {
			return
		}
		defer f.Close()
		if _, err := f.Seek(offset, 0); err != nil {
			return
		}
		buf := make([]byte, 32*1024)
		for {
			n, _ := f.Read(buf)
			if n <= 0 {
				break
			}
			offset += int64(n)

			for _, line := range splitSSE(buf[:n]) {
				_, _ = c.Writer.WriteString("data: " + line + "\n")
			}
			_, _ = c.Writer.WriteString("\n")
			flusher.Flush()
		}
	}

	ctx := c.Request.Context()
	ticker := time.NewTicker(500 * time.Millisecond)
	defer ticker.Stop()
	instanceID := auth.InstanceID(c)
	for {
		sendNew()
		if serverRunTerminal(instanceID, runID, serverID) {
			sendNew()
			_, _ = c.Writer.WriteString("event: done\ndata: end\n\n")
			flusher.Flush()
			return
		}
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
		}
	}
}

func serverRunTerminal(instanceID, runID, serverID string) bool {
	r, err := services.GetRun(instanceID, runID)
	if err != nil {
		return true
	}
	for _, sr := range r.ServerRuns {
		if sr.ServerID == serverID {
			switch sr.Status {
			case "success", "failed", "skipped", "cancelled":
				return true
			}
			return false
		}
	}
	return true
}

func splitSSE(b []byte) []string {
	s := strings.ReplaceAll(string(b), "\r", "")
	return strings.Split(s, "\n")
}

func listSteps(c *gin.Context) {
	steps, err := services.ListSteps(auth.InstanceID(c))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, steps)
}

func stepUsage(c *gin.Context) {
	counts, err := services.StepUsageCounts(auth.InstanceID(c))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, counts)
}

func createStep(c *gin.Context) {
	var s models.WorkflowStep
	if err := c.ShouldBindJSON(&s); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	out, err := services.CreateStep(auth.InstanceID(c), s)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	services.LogEvent(auth.InstanceID(c), "workflow.step_created", actorFromCtx(c), "", out.StepID, fmt.Sprintf("step '%s' created", out.Name))
	c.JSON(http.StatusCreated, out)
}

func updateStep(c *gin.Context) {
	var s models.WorkflowStep
	if err := c.ShouldBindJSON(&s); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if err := services.UpdateStep(auth.InstanceID(c), c.Param("id"), s); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	services.LogEvent(auth.InstanceID(c), "workflow.step_updated", actorFromCtx(c), "", c.Param("id"), "step updated")
	c.JSON(http.StatusOK, gin.H{"updated": true})
}

func deleteStep(c *gin.Context) {
	if err := services.DeleteStep(auth.InstanceID(c), c.Param("id")); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	services.LogEvent(auth.InstanceID(c), "workflow.step_deleted", actorFromCtx(c), "", c.Param("id"), "step deleted")
	c.JSON(http.StatusOK, gin.H{"deleted": true})
}

func exportStep(c *gin.Context) {
	b, err := services.ExportStep(auth.InstanceID(c), c.Param("id"))
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
		return
	}
	c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=step-%s.json", c.Param("id")))
	c.Data(http.StatusOK, "application/json", b)
}

func seedDefaults(c *gin.Context) {
	created, updated, err := services.SeedDefaultSteps(auth.InstanceID(c))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	services.LogEvent(auth.InstanceID(c), "workflow.defaults_synced", actorFromCtx(c), "", "", fmt.Sprintf("default steps synced: %d created, %d updated", created, updated))
	c.JSON(http.StatusOK, gin.H{"created": created, "updated": updated})
}

const maxStepBodyBytes = 1 << 20

func importStep(c *gin.Context) {
	c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxStepBodyBytes)
	body, err := io.ReadAll(c.Request.Body)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	out, err := services.ImportStepToLibrary(auth.InstanceID(c), body)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	services.LogEvent(auth.InstanceID(c), "workflow.step_imported", actorFromCtx(c), "", out.StepID, fmt.Sprintf("step '%s' imported", out.Name))
	c.JSON(http.StatusCreated, out)
}

func parseStep(c *gin.Context) {
	c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxStepBodyBytes)
	body, err := io.ReadAll(c.Request.Body)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	s, err := services.ParseStepDoc(body)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, s)
}

func listWorkflows(c *gin.Context) {
	wfs, err := services.ListWorkflows(auth.InstanceID(c))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, wfs)
}

func createWorkflow(c *gin.Context) {
	var w models.Workflow
	if err := c.ShouldBindJSON(&w); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	out, err := services.CreateWorkflow(auth.InstanceID(c), w)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	services.LogEvent(auth.InstanceID(c), "workflow.created", actorFromCtx(c), "", out.WorkflowID, fmt.Sprintf("workflow '%s' created", out.Name))
	c.JSON(http.StatusCreated, out)
}

func getWorkflow(c *gin.Context) {
	w, err := services.GetWorkflow(auth.InstanceID(c), c.Param("id"))
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, w)
}

func updateWorkflow(c *gin.Context) {
	var w models.Workflow
	if err := c.ShouldBindJSON(&w); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if err := services.UpdateWorkflow(auth.InstanceID(c), c.Param("id"), w); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	services.LogEvent(auth.InstanceID(c), "workflow.updated", actorFromCtx(c), "", c.Param("id"), "workflow updated")
	updated, err := services.GetWorkflow(auth.InstanceID(c), c.Param("id"))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, updated)
}

func deleteWorkflow(c *gin.Context) {
	if err := services.DeleteWorkflow(auth.InstanceID(c), c.Param("id")); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	services.LogEvent(auth.InstanceID(c), "workflow.deleted", actorFromCtx(c), "", c.Param("id"), "workflow deleted")
	c.JSON(http.StatusOK, gin.H{"deleted": true})
}

func runWorkflow(c *gin.Context) {
	runID, err := services.TriggerWorkflow(auth.InstanceID(c), c.Param("id"), actorFromCtx(c))
	if err != nil {
		c.JSON(http.StatusServiceUnavailable, gin.H{"error": err.Error()})
		return
	}
	services.LogEvent(auth.InstanceID(c), "workflow.run_triggered", actorFromCtx(c), "", c.Param("id"), fmt.Sprintf("run %s triggered", runID))
	c.JSON(http.StatusAccepted, gin.H{"run_id": runID})
}

func listWorkflowRuns(c *gin.Context) {
	limit := int64(50)
	if l := c.Query("limit"); l != "" {
		if n, err := strconv.ParseInt(l, 10, 64); err == nil && n > 0 {
			limit = n
		}
	}
	runs, err := services.ListRuns(auth.InstanceID(c), c.Param("id"), limit)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, runs)
}

func getRun(c *gin.Context) {
	r, err := services.GetRun(auth.InstanceID(c), c.Param("runId"))
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, r)
}

func cancelRun(c *gin.Context) {
	if err := services.CancelRun(auth.InstanceID(c), c.Param("runId")); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	services.LogEvent(auth.InstanceID(c), "workflow.run_cancelled", actorFromCtx(c), "", c.Param("runId"), "run cancelled")
	c.JSON(http.StatusOK, gin.H{"cancelled": true})
}
