package api

import (
	"database/sql"
	"log/slog"
	"net/http"
	"runtime/debug"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
	"github.com/syncserver/internal/auth"
	"github.com/syncserver/internal/config"
	"github.com/syncserver/internal/sshmanager"
	"github.com/syncserver/internal/syncengine"
	"github.com/syncserver/internal/webui"
)

type Server struct {
	router *chi.Mux
	cfg    *config.Config
	engine *syncengine.Engine
}

func NewServer(cfg *config.Config, db *sql.DB, engine *syncengine.Engine) *Server {
	auth.InitJWTManager(cfg.Auth.JWTSecret, cfg.Auth.JWTExpiryH)

	r := chi.NewRouter()
	r.Use(middleware.RequestID)
	r.Use(middleware.RealIP)
	r.Use(middleware.Logger)
	r.Use(recoverer)

	s := &Server{router: r, cfg: cfg, engine: engine}

	authHandler := NewAuthHandler(db)
	machineHandler := NewMachineHandler(db)
	syncPairHandler := NewSyncPairHandler(db)
	jobHandler := NewJobHandler(db, engine)
	sseHandler := NewSSEHandler(engine)
	sshKeyHandler := NewSSHKeyHandler(db, cfg)

	r.Route("/api", func(r chi.Router) {
		r.Route("/auth", func(r chi.Router) {
			r.Post("/login", authHandler.Login)
			r.Post("/logout", authHandler.Logout)
			r.With(auth.RequireAuth).Get("/me", authHandler.Me)
		})

		r.With(auth.RequireAuth).Route("/machines", func(r chi.Router) {
			r.Get("/", machineHandler.List)
			r.Post("/", machineHandler.Create)
			r.Get("/{id}", machineHandler.Get)
			r.Put("/{id}", machineHandler.Update)
			r.Delete("/{id}", machineHandler.Delete)
		})

		r.With(auth.RequireAuth).Route("/sync-pairs", func(r chi.Router) {
			r.Get("/", syncPairHandler.List)
			r.Post("/", syncPairHandler.Create)
			r.Get("/{id}", syncPairHandler.Get)
			r.Put("/{id}", syncPairHandler.Update)
			r.Delete("/{id}", syncPairHandler.Delete)
			r.Post("/{id}/run", jobHandler.TriggerRun)
		})

		r.With(auth.RequireAuth).Route("/jobs", func(r chi.Router) {
			r.Get("/", jobHandler.List)
			r.Get("/{id}", jobHandler.Get)
			r.Post("/{id}/cancel", jobHandler.Cancel)
			r.Get("/{id}/log", jobHandler.GetLog)
			r.Get("/{id}/log/download", jobHandler.DownloadLog)
			r.Get("/{id}/log/stream", sseHandler.StreamJob)
		})

		r.With(auth.RequireAuth).Get("/jobs/stream", sseHandler.StreamAll)

		r.With(auth.RequireAuth).Get("/settings/pubkey", func(w http.ResponseWriter, r *http.Request) {
			_, _, pubKey, _ := sshmanager.EnsureServerKey(cfg.SSHDir())
			w.Header().Set("Content-Type", "text/plain")
			w.Write([]byte(pubKey))
		})

		r.With(auth.RequireAuth).Route("/ssh-keys", func(r chi.Router) {
			r.Get("/", sshKeyHandler.List)
			r.Post("/", sshKeyHandler.Create)
			r.Get("/{id}", sshKeyHandler.Get)
			r.Delete("/{id}", sshKeyHandler.Delete)
			r.Get("/{id}/private", sshKeyHandler.DownloadPrivate)
		})
	})

	r.Get("/health", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("ok"))
	}))

	r.NotFound(func(w http.ResponseWriter, r *http.Request) {
		webui.ServeSPA().ServeHTTP(w, r)
	})

	return s
}

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	s.router.ServeHTTP(w, r)
}

func recoverer(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if err := recover(); err != nil {
				slog.Error("panic recovered",
					"error", err,
					"stack", string(debug.Stack()),
					"method", r.Method,
					"path", r.URL.Path,
				)
				w.Header().Set("Content-Type", "application/json")
				w.WriteHeader(http.StatusInternalServerError)
				http.Error(w, `{"error":"internal server error"}`, http.StatusInternalServerError)
			}
		}()
		next.ServeHTTP(w, r)
	})
}
