package acmecert

import (
	"net/http"
	"strings"
	"sync"

	"mailgoserver/internal/toolbox"
)

// HTTP01Server is a long-lived HTTP server dedicated to the [LetsEncryptHTTP] HTTP-01
// challenge — started once at boot (if enabled) and kept running for the whole process
// lifetime, unlike lego's own http01.ProviderServer (challenge/http01), which binds and
// unbinds the port on every single obtain/renew. Staying up lets an operator behind
// NAT/a reverse proxy verify their port-forwarding actually reaches this host (curl it
// directly, get a 200) without waiting for or burning a real, rate-limited ACME attempt.
// It implements lego's challenge.Provider interface (Present/CleanUp) so a Manager hands
// it token/keyAuth pairs as they come, instead of each obtain spinning up its own
// server.
type HTTP01Server struct {
	mu     sync.RWMutex
	tokens map[string]string // token -> keyAuth

	server *http.Server
}

func NewHTTP01Server() *HTTP01Server {
	s := &HTTP01Server{tokens: map[string]string{}}
	mux := http.NewServeMux()
	mux.HandleFunc("/.well-known/acme-challenge/", s.serveChallenge)
	mux.HandleFunc("/", s.serveHealth)
	s.server = &http.Server{Handler: mux}
	return s
}

func (s *HTTP01Server) serveHealth(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/plain")
	w.WriteHeader(http.StatusOK)
	w.Write([]byte("ok"))
}

func (s *HTTP01Server) serveChallenge(w http.ResponseWriter, r *http.Request) {
	token := strings.TrimPrefix(r.URL.Path, "/.well-known/acme-challenge/")
	s.mu.RLock()
	keyAuth, ok := s.tokens[token]
	s.mu.RUnlock()
	if !ok {
		http.NotFound(w, r)
		return
	}
	w.Header().Set("Content-Type", "text/plain")
	w.Write([]byte(keyAuth))
}

// Start binds addr (e.g. ":80") and serves in the background until the process exits.
// Call once at boot. A bind failure (port already in use, missing
// CAP_NET_BIND_SERVICE) is logged rather than crashing the process — HTTP-01
// obtain/renew attempts then fail with a clear error from lego instead, same as any
// other misconfiguration surfaced via Status.LastError.
func (s *HTTP01Server) Start(addr string, logger *toolbox.Logger) {
	s.server.Addr = addr
	go func() {
		if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			logger.Error("HTTP-01 challenge server: %v", err)
		}
	}()
}

// Present and CleanUp implement github.com/go-acme/lego/v4/challenge.Provider.
func (s *HTTP01Server) Present(domain, token, keyAuth string) error {
	s.mu.Lock()
	s.tokens[token] = keyAuth
	s.mu.Unlock()
	return nil
}

func (s *HTTP01Server) CleanUp(domain, token, keyAuth string) error {
	s.mu.Lock()
	delete(s.tokens, token)
	s.mu.Unlock()
	return nil
}
