// hooks_resolve.go — server-side resolution of pre/post hooks for a
// backup dispatch (P2R-11). The agent receives plaintext hook bodies
// in CommandRunPayload; this file is where the AEAD blob on the
// source group (or the host's default) gets decrypted into the
// strings the wire payload carries.
//
// Resolution order:
//  1. source_group.<phase>_hook  (per-group override)
//  2. host.<phase>_hook_default  (host-wide default)
//  3. ""                          (no hook → agent skips that phase)
//
// Decrypt errors are logged and treated as "no hook configured" so
// a malformed blob can't poison every backup. The audit trail
// captures the underlying state regardless.
package http

import (
	"log/slog"

	"gitea.dcglab.co.uk/steve/restic-manager/internal/store"
)

// resolveBackupHooks returns the (pre, post) plaintext hook strings
// the agent should run around the backup. Both are empty when no
// hook is configured at either level.
func (s *Server) resolveBackupHooks(host *store.Host, g *store.SourceGroup) (pre, post string) {
	if s.deps.AEAD == nil {
		return "", ""
	}
	pre = s.decryptHookOrFallback(g.PreHook, host.PreHookDefault, host.ID, "pre")
	post = s.decryptHookOrFallback(g.PostHook, host.PostHookDefault, host.ID, "post")
	return pre, post
}

// decryptHookOrFallback returns the per-group hook decrypted, or
// (when that's empty) the host default decrypted, or "" if neither
// is configured. Decrypt failures log and degrade to empty.
func (s *Server) decryptHookOrFallback(group, hostDefault, hostID, phase string) string {
	tryDecrypt := func(blob, slot string) (string, bool) {
		if blob == "" {
			return "", false
		}
		plain, err := s.deps.AEAD.Decrypt(blob, []byte("hook:"+hostID+":"+slot+":"+phase))
		if err != nil {
			slog.Error("decrypt hook", "host_id", hostID, "phase", phase, "slot", slot, "err", err)
			return "", false
		}
		return string(plain), true
	}
	if v, ok := tryDecrypt(group, "group"); ok {
		return v
	}
	if v, ok := tryDecrypt(hostDefault, "host"); ok {
		return v
	}
	return ""
}

// EncryptHookForGroup encrypts a hook body for storage on a source
// group. Caller passes the plaintext from a UI form; an empty body
// returns "" so the store persists NULL (cleared).
func (s *Server) EncryptHookForGroup(hostID, phase, body string) (string, error) {
	if body == "" {
		return "", nil
	}
	return s.deps.AEAD.Encrypt([]byte(body), []byte("hook:"+hostID+":group:"+phase))
}

// EncryptHookForHost is the host-default twin of EncryptHookForGroup.
func (s *Server) EncryptHookForHost(hostID, phase, body string) (string, error) {
	if body == "" {
		return "", nil
	}
	return s.deps.AEAD.Encrypt([]byte(body), []byte("hook:"+hostID+":host:"+phase))
}
