package server

import (
	"context"
	"encoding/json"
	"log/slog"
	"net/http"
	"strings"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
	"github.com/jackc/pgx/v5/pgxpool"

	"time"

	"git.fabledsword.com/bvandeusen/minstrel/internal/api"
	"git.fabledsword.com/bvandeusen/minstrel/internal/auth"
	"git.fabledsword.com/bvandeusen/minstrel/internal/config"
	"git.fabledsword.com/bvandeusen/minstrel/internal/library"
	"git.fabledsword.com/bvandeusen/minstrel/internal/playevents"
	"git.fabledsword.com/bvandeusen/minstrel/internal/subsonic"
	"git.fabledsword.com/bvandeusen/minstrel/web"
)

// ScanTrigger is the subset of the scanner the HTTP handler needs. Kept as an
// interface so tests can stub it without touching the DB.
type ScanTrigger interface {
	Scan(ctx context.Context) (library.Stats, error)
}

type Server struct {
	Logger            *slog.Logger
	Pool              *pgxpool.Pool
	Scanner           ScanTrigger
	SubsonicCfg       subsonic.Config
	EventsCfg         config.EventsConfig
	RecommendationCfg config.RecommendationConfig
}

func New(logger *slog.Logger, pool *pgxpool.Pool, scanner ScanTrigger, subCfg subsonic.Config, eventsCfg config.EventsConfig, recCfg config.RecommendationConfig) *Server {
	return &Server{Logger: logger, Pool: pool, Scanner: scanner, SubsonicCfg: subCfg, EventsCfg: eventsCfg, RecommendationCfg: recCfg}
}

func (s *Server) Router() http.Handler {
	r := chi.NewRouter()
	r.Use(middleware.RequestID)
	r.Use(middleware.Recoverer)

	r.Get("/healthz", s.handleHealthz)

	if s.Pool != nil {
		writer := playevents.NewWriter(
			s.Pool, s.Logger,
			time.Duration(s.EventsCfg.SessionTimeoutMinutes)*time.Minute,
			s.EventsCfg.SkipMaxCompletionRatio,
			s.EventsCfg.SkipMaxDurationPlayedMs,
		)
		api.Mount(r, s.Pool, s.Logger, writer, s.RecommendationCfg)
		r.Route("/api/admin", func(admin chi.Router) {
			admin.Use(auth.RequireAdmin(s.Pool))
			if s.Scanner != nil {
				admin.Post("/scan", s.handleAdminScan)
			}
		})
		subsonic.Mount(r, s.Pool, s.Logger, s.SubsonicCfg, writer)
	}

	spa := web.Handler()
	r.NotFound(func(w http.ResponseWriter, req *http.Request) {
		p := req.URL.Path
		if strings.HasPrefix(p, "/api/") || strings.HasPrefix(p, "/rest/") {
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusNotFound)
			_, _ = w.Write([]byte(`{"error":{"code":"not_found","message":"not found"}}`))
			return
		}
		spa.ServeHTTP(w, req)
	})
	return r
}

func (s *Server) handleHealthz(w http.ResponseWriter, _ *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	_ = json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}

// handleAdminScan runs a scan synchronously and returns the resulting stats.
// Kept synchronous for v1: libraries are small and the operator can tell when
// it's done. Async jobs with status polling are a later-milestone concern.
func (s *Server) handleAdminScan(w http.ResponseWriter, r *http.Request) {
	stats, err := s.Scanner.Scan(r.Context())
	w.Header().Set("Content-Type", "application/json")
	if err != nil {
		s.Logger.Error("admin scan failed", "err", err)
		w.WriteHeader(http.StatusInternalServerError)
		_ = json.NewEncoder(w).Encode(map[string]any{"error": err.Error(), "stats": stats})
		return
	}
	w.WriteHeader(http.StatusOK)
	_ = json.NewEncoder(w).Encode(stats)
}
