// Package api provides HTTP routing and handlers for the persona-api service.
package api

import (
	"time"

	emailpkg "git.threesix.ai/jordan/persona-community-5/pkg/email"
	"git.threesix.ai/jordan/persona-community-5/pkg/app"
	"git.threesix.ai/jordan/persona-community-5/pkg/auth"
	"git.threesix.ai/jordan/persona-community-5/pkg/middleware"
	"git.threesix.ai/jordan/persona-community-5/pkg/queue"
	"git.threesix.ai/jordan/persona-community-5/pkg/realtime"
	"git.threesix.ai/jordan/persona-community-5/pkg/storage"
	"git.threesix.ai/jordan/persona-community-5/services/persona-api/internal/api/handlers"
	"git.threesix.ai/jordan/persona-community-5/services/persona-api/internal/config"
	"git.threesix.ai/jordan/persona-community-5/services/persona-api/internal/port"
	"git.threesix.ai/jordan/persona-community-5/services/persona-api/internal/service"
)

// RegisterRoutes registers all HTTP routes for the service.
// Routes are mounted under /api/persona-api to match the ingress path routing.
// This allows the monorepo to expose multiple services under a single domain:
//   - https://domain/api/persona-api/health
//   - https://domain/api/persona-api/examples
//   - https://domain/api/persona-api/events?channel=user:123 (SSE)
func RegisterRoutes(application *app.App, deps *Dependencies) {
	logger := application.Logger()
	cfg := config.Load()

	// Initialize handlers with injected services
	healthHandler := handlers.NewHealth(logger)
	exampleHandler := handlers.NewExample(deps.ExampleService, logger)
	authHandler := handlers.NewAuth(deps.AuthService, logger)
	generateHandler := handlers.NewGenerate(deps.Queue, deps.JobReader, deps.SSEHub, logger)
	chatHandler := handlers.NewChat(deps.Queue, deps.SSEHub, logger)
	mediaHandler := handlers.NewMedia(deps.Store, deps.MediaRepo, logger)
	albumHandler := handlers.NewAlbum(deps.AlbumService, logger)
	personaHandler := handlers.NewPersona(deps.PersonaService, logger)

	// Build and mount OpenAPI spec
	spec := NewServiceSpec()
	application.EnableDocs(spec)

	// JWT validator for protected routes
	jwtValidator := auth.NewJWTValidator(auth.JWTConfig{
		Secret: []byte(cfg.JWTSecret),
		Issuer: "persona-community-5",
	})

	// Dev email preview (development only — not mounted in production).
	if cfg.AppConfig.Environment == "development" && deps.EmailRenderer != nil {
		devHandler := emailpkg.NewDevHandler(deps.EmailRenderer)
		application.Router().Get("/dev/emails", devHandler.List)
		application.Router().Get("/dev/emails/{purpose}", devHandler.Preview)
	}

	// Register API routes under /api/{service-name} to match ingress path routing.
	// The ingress routes /api/persona-api/* to this service.
	application.Route("/api/persona-api", func(r app.Router) {
		r.Get("/health", healthHandler.Check)

		// ----- Public auth routes (rate-limited) -----
		// Auth attempts: 20/min per IP (login, register, verify, reset).
		authAttemptLimit := middleware.RateLimit(middleware.RateLimitConfig{Requests: 20, Window: time.Minute})
		// Code sends: 5/min per IP (prevents email bombing via OTP/magic-link/forgot-password).
		codeSendLimit := middleware.RateLimit(middleware.RateLimitConfig{Requests: 5, Window: time.Minute})

		r.Group(func(r app.Router) {
			r.Use(authAttemptLimit)
			r.Post("/auth/login", app.Wrap(authHandler.Login))
			r.Post("/auth/register", app.Wrap(authHandler.Register))
			r.Post("/auth/otp/verify", app.Wrap(authHandler.VerifyOTP))
			r.Post("/auth/magic-link/verify", app.Wrap(authHandler.VerifyMagicLink))
			r.Post("/auth/reset-password", app.Wrap(authHandler.ResetPassword))
		})
		r.Group(func(r app.Router) {
			r.Use(codeSendLimit)
			r.Post("/auth/otp/send", app.Wrap(authHandler.SendOTP))
			r.Post("/auth/magic-link", app.Wrap(authHandler.SendMagicLink))
			r.Post("/auth/forgot-password", app.Wrap(authHandler.ForgotPassword))
		})

		// Refresh accepts expired tokens (still validates signature).
		// The service layer checks session validity to prevent abuse.
		r.Group(func(r app.Router) {
			r.Use(auth.Middleware(auth.MiddlewareConfig{
				Validator:    jwtValidator,
				AllowExpired: true,
			}))
			r.Post("/auth/refresh", app.Wrap(authHandler.RefreshToken))
		})

		// Session checker for revocation enforcement.
		sessionChecker := deps.AuthService.CheckSession

		// ----- Protected auth routes -----
		r.Group(func(r app.Router) {
			r.Use(auth.Middleware(auth.MiddlewareConfig{
				Validator: jwtValidator,
			}))
			r.Use(auth.SessionCheck(sessionChecker))

			r.Get("/auth/me", app.Wrap(authHandler.Me))
			r.Put("/auth/me", app.Wrap(authHandler.UpdateMe))
			r.Post("/auth/change-password", app.Wrap(authHandler.ChangePassword))
			r.Post("/auth/logout", app.Wrap(authHandler.Logout))
			r.Post("/auth/verify-email/send", app.Wrap(authHandler.SendVerifyEmail))
			r.Post("/auth/verify-email", app.Wrap(authHandler.VerifyEmail))
			r.Get("/auth/sessions", app.Wrap(authHandler.ListSessions))
			r.Delete("/auth/sessions", app.Wrap(authHandler.RevokeAllSessions))
			r.Delete("/auth/sessions/{id}", app.Wrap(authHandler.RevokeSession))
		})

		// ----- SSE Events -----
		// Server-Sent Events for async job updates (generation progress, etc.)
		r.Mount("/events", generateHandler.Events())

		// ----- Example routes -----
		// Public routes (no auth required)
		r.Get("/examples", app.Wrap(exampleHandler.List))
		r.Get("/examples/{id}", app.Wrap(exampleHandler.Get))

		// Protected routes (auth required when enabled)
		r.Group(func(r app.Router) {
			if cfg.AuthEnabled {
				r.Use(auth.Middleware(auth.MiddlewareConfig{
					Validator: jwtValidator,
				}))
			}

			r.Post("/examples", app.Wrap(exampleHandler.Create))
			r.Put("/examples/{id}", app.Wrap(exampleHandler.Update))
			r.Delete("/examples/{id}", app.Wrap(exampleHandler.Delete))
		})

		// ----- Chat + Generate + Media routes (auth required) -----
		// Auth is required because SSE events are delivered to user:<userId> channels.
		// Without a real user identity, events go to user:anonymous and never reach the client.
		r.Group(func(r app.Router) {
			r.Use(auth.Middleware(auth.MiddlewareConfig{
				Validator: jwtValidator,
			}))
			r.Use(auth.SessionCheck(sessionChecker))

			// Chat messaging
			r.Post("/chat/messages", app.Wrap(chatHandler.SendMessage))

			// Media generation (all queue-based, returns 202)
			r.Post("/generate/image", app.Wrap(generateHandler.GenerateImage))
			r.Post("/generate/video", app.Wrap(generateHandler.GenerateVideo))
			r.Post("/generate/text", app.Wrap(generateHandler.GenerateText))
			r.Get("/generate/jobs/{id}", app.Wrap(generateHandler.GetJobStatus))

			// Media library (upload, list, delete)
			r.Mount("/media", mediaHandler.Routes())

			// Album generation (anchor + shots)
			r.Get("/albums", app.Wrap(albumHandler.List))
			r.Post("/albums", app.Wrap(albumHandler.Create))
			r.Get("/albums/{id}", app.Wrap(albumHandler.Get))
			r.Delete("/albums/{id}", app.Wrap(albumHandler.Delete))
			r.Post("/albums/{id}/anchor", app.Wrap(albumHandler.GenerateAnchor))
			r.Post("/albums/{id}/shots", app.Wrap(albumHandler.GenerateAllShots))
			r.Post("/albums/{id}/shots/{index}", app.Wrap(albumHandler.GenerateShot))
			r.Delete("/albums/{id}/shots/{index}", app.Wrap(albumHandler.ResetShot))

			// Persona CRUD (Create enqueues generate_spec, returns 202)
			r.Post("/personas", app.Wrap(personaHandler.Create))
			r.Get("/personas", app.Wrap(personaHandler.List))
			r.Get("/personas/{id}", app.Wrap(personaHandler.GetByID))
		})
	})
}

// Dependencies holds all service dependencies for route registration.
type Dependencies struct {
	ExampleService *service.ExampleService
	AuthService    *service.AuthService
	AlbumService   *service.AlbumService
	PersonaService *service.PersonaService
	Queue          queue.Producer
	JobReader      queue.JobReader
	SSEHub         *realtime.SSEHub
	Store          storage.Store
	MediaRepo      port.MediaRepository
	EmailRenderer  *emailpkg.Renderer
}
