package main

import (
	"encoding/json"
	"net/http"
	"runtime"
	"strconv"
)

type Server struct {
	serviceProbe   ServiceProbe
	metricProbe    MetricProbe
	portalServices PortalServiceProvider
	auth           AuthConfig
}

func NewServer() *Server {
	serviceProbe := NewServiceProbe()
	return &Server{
		serviceProbe:   serviceProbe,
		metricProbe:    NewMetricProbe(),
		portalServices: NewPortalServiceProvider(),
		auth:           NewAuthConfig(),
	}
}

func (s *Server) ListenAndServe(addr string) error {
	return http.ListenAndServe(addr, s.withCORS(s.routes()))
}

func (s *Server) routes() http.Handler {
	mux := http.NewServeMux()
	mux.HandleFunc("/auth/status", s.authStatusHandler)
	mux.HandleFunc("/auth/reset", s.resetAuthHandler)
	mux.HandleFunc("/health", s.healthHandler)
	mux.HandleFunc("/services", s.servicesHandler)
	mux.HandleFunc("/portal/services", s.portalServicesHandler)
	mux.HandleFunc("/metrics/simple", s.metricsHandler)
	return mux
}

func (s *Server) withCORS(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		origin := r.Header.Get("Origin")
		if allowedOrigin(origin) {
			w.Header().Set("Access-Control-Allow-Origin", origin)
		} else {
			w.Header().Set("Access-Control-Allow-Origin", "http://127.0.0.1:17000")
		}
		w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Bridge-Token, X-XWorkspace-Token")
		if r.Method == http.MethodOptions {
			w.WriteHeader(http.StatusNoContent)
			return
		}
		next.ServeHTTP(w, r)
	})
}

func allowedOrigin(origin string) bool {
	switch origin {
	case "http://127.0.0.1:17000", "http://localhost:17000", "http://127.0.0.1:3000", "https://console.svc.plus":
		return true
	default:
		return false
	}
}

func (s *Server) authStatusHandler(w http.ResponseWriter, r *http.Request) {
	writeJSON(w, AuthStatusResponse{Required: s.auth.Required()})
}

func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) {
	if !s.requireAuth(w, r) {
		return
	}
	services := s.serviceProbe.Probe()
	writeJSON(w, HealthResponse{
		Status:   "ok",
		Arch:     runtime.GOARCH,
		OS:       runtime.GOOS,
		CPU:      runtime.NumCPU(),
		Memory:   "unknown",
		Disk:     "unknown",
		Services: services,
		Metrics:  s.metricProbe.Probe(services),
	})
}

func (s *Server) servicesHandler(w http.ResponseWriter, r *http.Request) {
	if !s.requireAuth(w, r) {
		return
	}
	writeJSON(w, s.serviceProbe.Probe())
}

func (s *Server) portalServicesHandler(w http.ResponseWriter, r *http.Request) {
	if !s.requireAuth(w, r) {
		return
	}
	writeJSON(w, PortalServicesResponse{Services: s.portalServices.Services()})
}

func (s *Server) metricsHandler(w http.ResponseWriter, r *http.Request) {
	if !s.requireAuth(w, r) {
		return
	}
	_, _ = w.Write([]byte("xworkspace_systemd_services " + strconv.Itoa(len(s.serviceProbe.Probe())) + "\n"))
}

func (s *Server) requireAuth(w http.ResponseWriter, r *http.Request) bool {
	if s.auth.Authorize(r) {
		return true
	}
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.WriteHeader(http.StatusUnauthorized)
	_ = json.NewEncoder(w).Encode(map[string]string{"error": "unauthorized"})
	return false
}

func writeJSON(w http.ResponseWriter, v any) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	enc := json.NewEncoder(w)
	enc.SetIndent("", "  ")
	if err := enc.Encode(v); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func (s *Server) resetAuthHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}
	var req ResetAuthRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "Invalid request body", http.StatusBadRequest)
		return
	}
	newToken, err := s.auth.ResetAuthToken(req.CurrentToken)
	if err != nil {
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
		w.WriteHeader(http.StatusUnauthorized)
		_ = json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
		return
	}
	writeJSON(w, map[string]string{"token": newToken})
}
