package bootstrap

import (
	"context"
	"encoding/json"
	"net"
	"net/http"
	"time"

	"go.uber.org/zap"

	"iop/apps/edge/internal/configrefresh"
)

// RefreshAdminServer is the Edge-local HTTP admin server for config refresh.
// It listens on a loopback address and accepts POST /refresh requests from
// the local CLI (iop-edge config refresh). The server is only started when
// refresh.enabled=true in the Edge config.
type RefreshAdminServer struct {
	listen  string
	runtime *Runtime
	logger  *zap.Logger
	server  *http.Server
	ln      net.Listener
}

func newRefreshAdminServer(listen string, rt *Runtime, logger *zap.Logger) *RefreshAdminServer {
	return &RefreshAdminServer{
		listen:  listen,
		runtime: rt,
		logger:  logger,
	}
}

func (s *RefreshAdminServer) Start(_ context.Context) error {
	ln, err := net.Listen("tcp", s.listen)
	if err != nil {
		return err
	}
	s.ln = ln

	mux := http.NewServeMux()
	mux.HandleFunc("/refresh", s.handleRefresh)

	s.server = &http.Server{
		Handler:      mux,
		ReadTimeout:  30 * time.Second,
		WriteTimeout: 30 * time.Second,
	}
	go func() {
		if err := s.server.Serve(ln); err != nil && err != http.ErrServerClosed {
			s.logger.Warn("refresh admin server exited", zap.Error(err))
		}
	}()
	s.logger.Info("refresh admin server listening", zap.String("addr", ln.Addr().String()))
	return nil
}

func (s *RefreshAdminServer) Stop(ctx context.Context) error {
	if s.server == nil {
		return nil
	}
	return s.server.Shutdown(ctx)
}

// Addr returns the actual bound address. Useful for port-0 tests.
func (s *RefreshAdminServer) Addr() string {
	if s.ln == nil {
		return ""
	}
	return s.ln.Addr().String()
}

func (s *RefreshAdminServer) handleRefresh(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}

	var req configrefresh.Request
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeJSON(w, http.StatusBadRequest, configrefresh.RejectedResult(req, "invalid request body: "+err.Error()))
		return
	}

	result, err := s.runtime.RefreshConfig(r.Context(), req)
	if err != nil {
		writeJSON(w, http.StatusInternalServerError, configrefresh.RejectedResult(req, "refresh error: "+err.Error()))
		return
	}

	httpStatus := http.StatusOK
	if result.Status == configrefresh.StatusRejected {
		httpStatus = http.StatusUnprocessableEntity
	}
	writeJSON(w, httpStatus, result)
}

func writeJSON(w http.ResponseWriter, status int, v interface{}) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(v)
}
