package admin

import (
	"database/sql"
	"log"
	"net/http"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
	"github.com/redis/go-redis/v9"
	"github.com/wangjia/pangolin/server/internal/codes"
	"github.com/wangjia/pangolin/server/internal/provision"
	"github.com/wangjia/pangolin/server/internal/provision/providers"
)

// NewRouter wires the full admin HTTP handler chain:
//
//	IP allowlist  →  [ /login, /static ]              (no session)
//	IP allowlist  →  session  →  [ everything else ]  (authenticated)
func NewRouter(h *Handlers, sessions *SessionStore, cfg *Config, sec *SecurityLog) http.Handler {
	ipAllow := func(next http.Handler) http.Handler { return NewIPAllow(cfg.AllowCIDRs, sec, next) }
	requireSession := func(next http.Handler) http.Handler { return NewSessionMiddleware(sessions, next) }

	r := chi.NewRouter()
	r.Use(middleware.Recoverer)
	r.Use(ipAllow)

	// Static assets (CSS/JS) — embedded; behind the allowlist but pre-auth.
	r.Handle("/static/*", http.FileServer(http.FS(assetsFS)))

	// Pre-auth login routes.
	r.Get("/login", h.LoginPage)
	r.Post("/login", h.LoginSubmit)

	// Authenticated routes.
	r.Group(func(pr chi.Router) {
		pr.Use(requireSession)
		pr.Get("/", h.Dashboard)
		pr.Post("/logout", h.Logout)
		pr.Get("/codes", h.CodesPage)
		pr.Post("/codes", h.CreateBatch)
		pr.Post("/codes/void", h.VoidBatch)
		pr.Get("/nodes", h.NodesPage)
		pr.Post("/nodes/op", h.NodeOp)
		pr.Get("/audit", h.AuditPage)
	})
	return r
}

// BuildServices assembles the downstream services. Codes (#3) and lifecycle (#5)
// are always real (DB-backed). Provisioning (#14) is real when the provision
// service initializes; otherwise it falls back to the not-ready stub and the UI
// greys out the replace control (Ready()==false).
func BuildServices(db *sql.DB, rdb *redis.Client, failMax int, lockDur time.Duration) Services {
	codeStore := codes.NewStore(db)
	codeSvc := codes.NewService(codeStore, rdb, failMax, lockDur)
	svc := Services{
		Codes:     NewCodesAdapter(codeSvc, codeStore),
		Lifecycle: NewRealLifecycle(db),
		Provision: NewStubProvision(),
	}
	provSvc, err := provision.NewService(provision.Config{
		Store:    provision.NewMySQLStore(db),
		Adapters: providers.NewRegistry(),
	})
	if err != nil {
		log.Printf("admin: provision service init failed (%v) — replace control disabled", err)
	} else {
		svc.Provision = NewRealProvision(provSvc)
	}
	return svc
}

// NewHandler builds the complete admin http.Handler from its dependencies.
func NewHandler(cfg *Config, db *sql.DB, rdb *redis.Client, svc Services, logger *log.Logger) (http.Handler, error) {
	if logger == nil {
		logger = log.Default()
	}
	store := NewDBStore(db)
	sessions := NewSessionStore(rdb, cfg.SessionTTL)
	sec := NewSecurityLog(store, logger)
	auth := NewAuthenticator(store, sessions, rdb, cfg, sec)
	handlers, err := NewHandlers(cfg, store, sessions, auth, svc, sec, logger)
	if err != nil {
		return nil, err
	}
	return NewRouter(handlers, sessions, cfg, sec), nil
}
