package httpserver

import (
	"net/http"
	"strings"

	"github.com/toki/oto/services/core/internal/cicdstate"
	"github.com/toki/oto/services/core/internal/runnerregistry"
)

type runnerControlDispatcher interface {
	DispatchQueuedJobs()
	CancelExecution(runnerID, execID, reason string) error
}

// registerRoutes registers all HTTP routes on the given ServeMux.
// This file exists to separate route registration logic from handler
// implementation, making it easier to refactor handlers and DTOs later.
// Note: HTTP runner job and registration endpoints are maintained as compatibility fallback routes.
func registerRoutes(mux *http.ServeMux, registry *runnerregistry.Registry, store *cicdstate.Store, dispatcher runnerControlDispatcher) {
	mux.HandleFunc("/healthz", withCORS(handleHealthz))
	mux.HandleFunc("/readyz", withCORS(handleReadyz))
	mux.HandleFunc("/api/v1/runners/register", withCORS(handleRunnerRegister(registry)))
	mux.HandleFunc("/api/v1/runners/bootstrap-command", withCORS(handleRunnerBootstrapCommand(registry)))
	mux.HandleFunc("/api/v1/runners/{id}/heartbeat", withCORS(handleRunnerHeartbeat(registry)))
	mux.HandleFunc("/api/v1/runners/{id}/disconnect", withCORS(handleRunnerDisconnect(registry)))
	mux.HandleFunc("/api/v1/runners/{id}", withCORS(handleRunnerResource(registry)))
	mux.HandleFunc("/bootstrap/oto-agent.sh", withCORS(handleServeBootstrapScript(embeddedBootstrapProvider{})))
	mux.HandleFunc("/bootstrap/oto-agent.ps1", withCORS(handleServeBootstrapPs1(embeddedBootstrapPs1Provider{})))
	mux.HandleFunc("/api/v1/", withCORS(handleRouterWithDispatcher(store, registry, dispatcher)))
}

func withCORS(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PATCH, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
		w.Header().Set("Access-Control-Max-Age", "600")
		if r.Method == http.MethodOptions {
			w.WriteHeader(http.StatusNoContent)
			return
		}
		next(w, r)
	}
}

// Extract path segments after "/api/v1/"
func apiPathSegments(path string) []string {
	if !strings.HasPrefix(path, "/api/v1/") {
		return nil
	}
	trimmed := strings.TrimPrefix(path, "/api/v1/")
	trimmed = strings.TrimRight(trimmed, "/")
	if trimmed == "" {
		return nil
	}
	return strings.Split(trimmed, "/")
}

func handleRouter(store *cicdstate.Store, registry *runnerregistry.Registry) http.HandlerFunc {
	return handleRouterWithDispatcher(store, registry, nil)
}

func handleRouterWithDispatcher(store *cicdstate.Store, registry *runnerregistry.Registry, dispatcher runnerControlDispatcher) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		parts := apiPathSegments(r.URL.Path)
		if parts == nil {
			http.NotFound(w, r)
			return
		}

		switch parts[0] {
		case "jobs":
			switch {
			case len(parts) == 1 && r.Method == http.MethodPost:
				handleCreateJob(store, dispatcher)(w, r)
			case len(parts) == 1 && r.Method == http.MethodGet:
				writeResponse(w, http.StatusMethodNotAllowed, map[string]string{"error": "method not allowed"})
				return
			case len(parts) == 2 && r.Method == http.MethodGet:
				handleGetJob(store)(w, r)
			case len(parts) == 3 && parts[2] == "executions" && r.Method == http.MethodPost:
				handleCreateExecution(store)(w, r)
			default:
				http.NotFound(w, r)
			}

		case "executions":
			if len(parts) < 2 {
				http.NotFound(w, r)
				return
			}
			switch {
			case len(parts) == 2 && r.Method == http.MethodGet:
				handleGetExecution(store)(w, r)
			case len(parts) == 3 && parts[2] == "logs" && r.Method == http.MethodPost:
				handleAppendLog(store)(w, r)
			case len(parts) == 3 && parts[2] == "logs" && r.Method == http.MethodGet:
				handleGetLogs(store)(w, r)
			case len(parts) == 3 && parts[2] == "artifacts" && r.Method == http.MethodPost:
				handleAppendArtifact(store)(w, r)
			case len(parts) == 3 && parts[2] == "artifacts" && r.Method == http.MethodGet:
				handleGetArtifacts(store)(w, r)
			default:
				http.NotFound(w, r)
			}

		case "runners":
			if len(parts) < 2 {
				http.NotFound(w, r)
				return
			}
			runnerID := parts[1]
			switch {
			case len(parts) == 3 && parts[2] == "status" && r.Method == http.MethodGet:
				handleRunnerStatus(store, registry, runnerID)(w, r)
			case len(parts) == 3 && parts[2] == "self-update" && r.Method == http.MethodPost:
				handleRunnerSelfUpdate(store, registry, runnerID)(w, r)
			case len(parts) == 4 && parts[2] == "jobs" && parts[3] == "claim" && r.Method == http.MethodPost:
				handleRunnerClaimJob(store, registry, runnerID)(w, r)
			case len(parts) == 5 && parts[2] == "executions" && parts[4] == "cancel" && r.Method == http.MethodPost:
				handleRunnerCancelExecution(store, registry, dispatcher, runnerID, parts[3])(w, r)
			case len(parts) == 5 && parts[2] == "executions" && parts[4] == "report" && r.Method == http.MethodPost:
				handleRunnerReportExecution(store, registry, runnerID, parts[3])(w, r)
			case len(parts) == 5 && parts[2] == "executions" && parts[4] == "logs" && r.Method == http.MethodPost:
				handleRunnerAppendLog(store, registry, runnerID, parts[3])(w, r)
			case len(parts) == 5 && parts[2] == "executions" && parts[4] == "artifacts" && r.Method == http.MethodPost:
				handleRunnerAppendArtifact(store, registry, runnerID, parts[3])(w, r)
			default:
				http.NotFound(w, r)
			}

		default:
			http.NotFound(w, r)
		}
	}
}
