package httpx

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

	"bountyboard/internal/auth"
	"bountyboard/internal/config"
	"bountyboard/internal/files"
	"bountyboard/internal/metrics"
	"bountyboard/internal/store"
	"bountyboard/internal/workperform"
)

// shutdownBudget is the §12 graceful-shutdown drain window.
const shutdownBudget = 15 * time.Second

type Server struct {
	cfg          *config.Config
	log          *slog.Logger
	metrics      *metrics.Registry
	store        *store.Store
	files        *files.Store
	templates    map[string]*template.Template
	oidc         *auth.OIDCClient
	mailer       *auth.Mailer
	loginLimiter *auth.RateLimiter
	checks       []ReadinessCheck
	startedAt    time.Time
	httpSrv      *http.Server

	// late-bound subsystem hooks (see providers.go)
	atomizer        BreakerInfo
	performer       BreakerInfo
	syncProvider    StatusProvider
	jobsProvider    StatusProvider
	syncTrigger     func(customerID string)
	publishFn       func(channel, event string, payload any)
	enqueueFn       func(ctx context.Context, kind string, payload any) (string, error)
	wsHandler       func(w http.ResponseWriter, r *http.Request, userID string)
	sendTo          func(userIDs []string, channel, event string, payload any)
	performerClient *workperform.Client
}

// SetSendTo wires targeted hub delivery (chat messages, typing).
func (s *Server) SetSendTo(f func(userIDs []string, channel, event string, payload any)) {
	s.sendTo = f
}

// SetPerformerClient wires the §5.2 client (also feeds the status panel).
func (s *Server) SetPerformerClient(c *workperform.Client) {
	s.performerClient = c
	s.performer = c
}

func New(cfg *config.Config, log *slog.Logger, reg *metrics.Registry, st *store.Store, fs *files.Store) *Server {
	templates, err := parseTemplates()
	if err != nil {
		// Templates are embedded; failure is a build defect caught by any
		// test or first boot, never a runtime condition.
		panic(fmt.Sprintf("parse templates: %v", err))
	}
	s := &Server{
		cfg:          cfg,
		log:          log,
		metrics:      reg,
		store:        st,
		files:        fs,
		templates:    templates,
		oidc:         auth.NewOIDCClient(cfg, log),
		mailer:       auth.NewMailer(cfg.SMTP),
		loginLimiter: auth.NewRateLimiter(10, 15*time.Minute),
		startedAt:    time.Now(),
	}

	mux := http.NewServeMux()
	mux.HandleFunc("GET /healthz", s.handleHealthz)
	mux.HandleFunc("GET /readyz", s.handleReadyz)
	mux.HandleFunc("GET /metricsz", s.handleMetricsz)
	s.routesAuth(mux)
	s.routesProfile(mux)
	s.routesAdmin(mux)
	s.routesTasks(mux)
	s.routesBoard(mux)
	s.routesConsultant(mux)
	s.routesWorkResults(mux)
	s.routesMessages(mux)
	s.routesMetrics(mux)
	s.routesDocs(mux)
	mux.HandleFunc("GET /ws", s.handleWS)
	s.routesWeb(mux)

	s.httpSrv = &http.Server{
		Addr:              fmt.Sprintf(":%d", cfg.AppPort),
		Handler:           s.withMiddleware(mux),
		ReadHeaderTimeout: 10 * time.Second,
		ErrorLog:          slog.NewLogLogger(log.Handler(), slog.LevelWarn),
	}
	return s
}

// Handler exposes the full middleware-wrapped handler for tests.
func (s *Server) Handler() http.Handler { return s.httpSrv.Handler }

// Run serves until ctx is canceled (SIGTERM/SIGINT), then drains in-flight
// requests within the shutdown budget.
func (s *Server) Run(ctx context.Context) error {
	errCh := make(chan error, 1)
	go func() {
		s.log.Info("http server listening", "addr", s.httpSrv.Addr, "baseUrl", s.cfg.AppBaseURL)
		if err := s.httpSrv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
			errCh <- err
			return
		}
		errCh <- nil
	}()

	select {
	case err := <-errCh:
		return fmt.Errorf("http server: %w", err)
	case <-ctx.Done():
	}

	s.log.Info("shutting down", "budget", shutdownBudget.String())
	shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownBudget)
	defer cancel()
	if err := s.httpSrv.Shutdown(shutdownCtx); err != nil {
		return fmt.Errorf("graceful shutdown: %w", err)
	}
	return <-errCh
}
