package api

import (
	"encoding/json"
	"net/http"
	"os/exec"
	"strings"

	"archivmail/internal/audit"
	"archivmail/internal/auth"
	"archivmail/internal/userstore"
)

// --- Service management ---

// allowedServices is the whitelist of systemd service names the admin may control.
var allowedServices = []string{
	"archivmail",
	"archivmail-web",
	"postgresql@17-main",
	"postfix",
	"nginx",
}

type ServiceStatus struct {
	Name            string `json:"name"`
	DisplayName     string `json:"display_name"`
	Active          string `json:"active"`            // active, inactive, failed, unknown
	Sub             string `json:"sub"`               // running, dead, exited, ...
	Enabled         string `json:"enabled"`           // enabled, disabled, static, unknown
	Description     string `json:"description"`
	ExternalBlocked *bool  `json:"external_blocked,omitempty"` // only set for archivmail
}

func isAllowedService(name string) bool {
	for _, s := range allowedServices {
		if s == name {
			return true
		}
	}
	return false
}

func systemctlShow(name string) ServiceStatus {
	svc := ServiceStatus{Name: name, DisplayName: name}
	out, err := exec.Command("systemctl", "show", name+".service",
		"--property=ActiveState,SubState,UnitFileState,Description",
		"--no-pager").Output()
	if err != nil {
		svc.Active = "unknown"
		svc.Sub = ""
		svc.Enabled = "unknown"
	} else {
		for _, line := range strings.Split(string(out), "\n") {
			k, v, ok := strings.Cut(line, "=")
			if !ok {
				continue
			}
			switch k {
			case "ActiveState":
				svc.Active = v
			case "SubState":
				svc.Sub = v
			case "UnitFileState":
				svc.Enabled = v
			case "Description":
				svc.Description = v
			}
		}
	}
	if name == "archivmail" {
		blocked := nftAPIBlocked()
		svc.ExternalBlocked = &blocked
	}
	return svc
}

// nftAPIBlocked reports whether external access to port 8080 is currently blocked.
func nftAPIBlocked() bool {
	out, err := exec.Command("sudo", "/usr/local/sbin/archivmail-nft", "status").Output()
	if err != nil {
		return false
	}
	return strings.TrimSpace(string(out)) == "blocked"
}

func (s *Server) handleListServices(w http.ResponseWriter, r *http.Request) {
	result := make([]ServiceStatus, 0, len(allowedServices))
	for _, name := range allowedServices {
		result = append(result, systemctlShow(name))
	}
	writeJSON(w, http.StatusOK, result)
}

func (s *Server) handleServiceAction(w http.ResponseWriter, r *http.Request) {
	// Only superadmin may start/stop/restart services
	sess := sessionFromCtx(r.Context())
	if sess == nil || !auth.HasRole(sess.Role, userstore.RoleSuperAdmin) {
		writeError(w, http.StatusForbidden, "superadmin required")
		return
	}

	name := r.PathValue("name")
	if !isAllowedService(name) {
		writeError(w, http.StatusBadRequest, "unknown service")
		return
	}

	var body struct {
		Action string `json:"action"`
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		writeError(w, http.StatusBadRequest, "invalid request")
		return
	}

	allowedActions := map[string]bool{
		"start": true, "stop": true, "restart": true,
		"enable": true, "disable": true,
	}
	nftActions := map[string]string{
		"block_external": "block",
		"allow_external": "unblock",
	}

	if nftArg, isNft := nftActions[body.Action]; isNft {
		if name != "archivmail" {
			writeError(w, http.StatusBadRequest, "external access control only available for archivmail")
			return
		}
		out, err := exec.Command("sudo", "/usr/local/sbin/archivmail-nft", nftArg).CombinedOutput()
		if err != nil {
			writeError(w, http.StatusInternalServerError, strings.TrimSpace(string(out)))
			return
		}
		sess := sessionFromCtx(r.Context())
		s.audlog.Log(audit.Entry{
			EventType: "service." + body.Action,
			Username:  sess.Username,
			TenantID:  sess.TenantID,
			IPAddress: s.remoteIP(r),
			Detail:    name,
			Success:   true,
		})
		writeJSON(w, http.StatusOK, systemctlShow(name))
		return
	}

	if !allowedActions[body.Action] {
		writeError(w, http.StatusBadRequest, "unknown action")
		return
	}

	out, err := exec.Command("sudo", "/usr/bin/systemctl", body.Action, name+".service").CombinedOutput()
	if err != nil {
		writeError(w, http.StatusInternalServerError, strings.TrimSpace(string(out)))
		return
	}

	s.audlog.Log(audit.Entry{
		EventType: "service." + body.Action,
		Username:  sess.Username,
		TenantID:  sess.TenantID,
		IPAddress: s.remoteIP(r),
		Detail:    name,
		Success:   true,
	})

	writeJSON(w, http.StatusOK, systemctlShow(name))
}
