package httpserver

import (
	"context"
	"embed"
	"fmt"
	"html/template"
	"log/slog"
	"net/http"
	"strings"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"

	"github.com/tim/cairnquire/apps/server/internal/auth"
	"github.com/tim/cairnquire/apps/server/internal/collaboration"
	"github.com/tim/cairnquire/apps/server/internal/config"
	"github.com/tim/cairnquire/apps/server/internal/docs"
	"github.com/tim/cairnquire/apps/server/internal/realtime"
	"github.com/tim/cairnquire/apps/server/internal/store"
	"github.com/tim/cairnquire/apps/server/internal/sync"
)

//go:embed templates/*.gohtml static/*
var assets embed.FS

type Dependencies struct {
	Config        config.Config
	Logger        *slog.Logger
	Documents     *docs.Service
	Repository    *docs.Repository
	ContentStore  *store.ContentStore
	Hub           *realtime.Hub
	SyncService   *sync.Service
	SyncRepo      *sync.Repository
	Auth          *auth.Service
	Collaboration *collaboration.Service
}

type Server struct {
	config        config.Config
	logger        *slog.Logger
	documents     *docs.Service
	repository    *docs.Repository
	contentStore  *store.ContentStore
	hub           *realtime.Hub
	syncService   *sync.Service
	syncRepo      *sync.Repository
	auth          *auth.Service
	collaboration *collaboration.Service
	authLimiter   *rateLimiter
	templates     *template.Template
	devUserID     string
}

func New(deps Dependencies) (http.Handler, error) {
	templates, err := template.New("").Funcs(template.FuncMap{
		"trimMd": func(path string) string {
			return strings.TrimSuffix(path, ".md")
		},
		"sub": func(a, b int) int {
			return a - b
		},
	}).ParseFS(assets, "templates/*.gohtml")
	if err != nil {
		return nil, err
	}

	server := &Server{
		config:        deps.Config,
		logger:        deps.Logger,
		documents:     deps.Documents,
		repository:    deps.Repository,
		contentStore:  deps.ContentStore,
		hub:           deps.Hub,
		syncService:   deps.SyncService,
		syncRepo:      deps.SyncRepo,
		auth:          deps.Auth,
		collaboration: deps.Collaboration,
		authLimiter:   newRateLimiter(5, time.Minute),
		templates:     templates,
	}

	if deps.Config.DevMode {
		devUser, err := deps.Auth.EnsureDevUser(context.Background())
		if err != nil {
			return nil, fmt.Errorf("ensure dev user: %w", err)
		}
		server.devUserID = devUser.ID
		deps.Logger.Info("dev mode enabled", "dev_user_id", devUser.ID)
	}

	router := chi.NewRouter()
	router.Use(middleware.RequestID)
	router.Use(middleware.RealIP)
	router.Use(middleware.Recoverer)
	router.Use(server.timeoutExceptWebSocket(30 * time.Second))
	router.Use(server.requestLogger)
	router.Use(server.securityHeaders)
	router.Use(server.setupMiddleware)
	router.Use(server.authMiddleware)

	router.Get("/", server.handleIndex)
	router.Get("/setup", server.handleSetupPage)
	router.Get("/login", server.handleLoginPage)
	router.Get("/password-reset", server.handlePasswordResetPage)
	router.Get("/account", server.handleAccountPage)
	router.Get("/account/tokens/new", server.handleTokenCreatePage)
	router.Get("/permissions", server.handlePermissionsPage)
	router.Post("/permissions", server.handlePermissionsForm)
	router.Get("/device/verify", server.handleDeviceVerifyPage)
	router.Get("/health", server.handleHealth)
	router.Get("/healthz", server.handleHealthz)
	router.Get("/readyz", server.handleReadyz)
	router.Get("/sw.js", server.handleServiceWorker)
	router.Get("/ws", server.handleWebSocket)
	router.Get("/api/auth/me", server.handleAuthMe)
	router.Post("/api/setup", server.handleSetup)
	router.Post("/api/auth/register/password", server.handlePasswordRegister)
	router.Post("/api/auth/login/password", server.handlePasswordLogin)
	router.Post("/api/auth/login/dev", server.handleDevLogin)
	router.Post("/api/auth/password/reset", server.handlePasswordReset)
	router.Post("/api/auth/logout", server.handleLogout)
	router.Post("/api/auth/password/change", server.handleChangePassword)
	router.Delete("/api/auth/account", server.handleDeleteAccount)
	router.Post("/api/auth/passkeys/me/register/begin", server.handleCurrentUserPasskeyRegisterBegin)
	router.Post("/api/auth/passkeys/me/register/finish", server.handleCurrentUserPasskeyRegisterFinish)
	router.Post("/api/auth/passkeys/register/begin", server.handlePasskeyRegisterBegin)
	router.Post("/api/auth/passkeys/register/finish", server.handlePasskeyRegisterFinish)
	router.Post("/api/auth/passkeys/login/begin", server.handlePasskeyLoginBegin)
	router.Post("/api/auth/passkeys/login/finish", server.handlePasskeyLoginFinish)
	router.Get("/api/tokens", server.handleListAPITokens)
	router.Post("/api/tokens", server.handleCreateAPIToken)
	router.Delete("/api/tokens/{id}", server.handleRevokeAPIToken)
	router.Post("/api/device/start", server.handleDeviceStart)
	router.Post("/api/device/approve", server.handleDeviceApprove)
	router.Post("/api/device/token", server.handleDeviceToken)
	router.Get("/api/device/verify", server.handleDeviceVerify)
	router.Post("/api/admin/login", server.handleAdminLogin)
	router.Get("/api/admin/users", server.handleAdminUsers)
	router.Post("/api/admin/users", server.handleAdminCreateUser)
	router.Patch("/api/admin/users/{id}/role", server.handleAdminUpdateUserRole)
	router.Get("/api/admin/auth-users", server.handleAdminAuthUsers)
	router.Patch("/api/admin/auth-users", server.handleAdminUpdateAuthUsers)
	router.Post("/api/admin/auth-users/{id}/password-reset", server.handleAdminSendPasswordReset)
	router.Get("/api/admin/settings", server.handleAdminSettings)
	router.Patch("/api/admin/settings", server.handleAdminSettingsUpdate)
	router.Get("/api/admin/workspace", server.handleAdminWorkspace)
	router.Post("/api/admin/workspace/sync", server.handleAdminWorkspaceSync)
	router.Get("/docs", server.handleDocsIndex)
	router.Get("/docs/*", server.handleDocument)
	router.Get("/api/documents", server.handleDocuments)
	router.Post("/api/documents/*", server.handleDocumentMutation)
	router.Get("/api/search", server.handleSearch)
	router.Get("/api/permissions", server.handlePermissionsAPI)
	router.Patch("/api/permissions", server.handlePermissionsAPI)
	router.Post("/api/uploads", server.handleUpload)
	router.Get("/api/attachments", server.handleAttachmentsList)
	router.Get("/attachments/{hash}", server.handleAttachment)

	// Collaboration endpoints
	router.Get("/api/comments", server.handleListComments)
	router.Post("/api/comments", server.handleCreateComment)
	router.Post("/api/comments/{id}/resolve", server.handleResolveComment)
	router.Get("/api/notifications", server.handleListNotifications)
	router.Post("/api/notifications/{id}/read", server.handleMarkNotificationRead)
	router.Post("/api/notifications/read-all", server.handleMarkAllNotificationsRead)
	router.Get("/api/notifications/bell", server.handleNotificationBell)
	router.Post("/api/watch/document", server.handleWatchDocument)
	router.Post("/api/watch/folder", server.handleWatchFolder)
	router.Delete("/api/watch/document", server.handleUnwatchDocument)
	router.Delete("/api/watch/folder", server.handleUnwatchFolder)
	router.Get("/api/watch/status", server.handleIsWatching)

	// Sync protocol endpoints
	router.Post("/api/sync/init", server.handleSyncInit)
	router.Post("/api/sync/delta", server.handleSyncDelta)
	router.Post("/api/sync/resolve", server.handleSyncResolve)
	router.Get("/api/content/{hash}", server.handleContentFetch)

	router.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(mustSub("static"))))

	return router, nil
}

func mustSub(path string) http.FileSystem {
	sub, err := fsSub(path)
	if err != nil {
		panic(err)
	}
	return http.FS(sub)
}
