// Command callback-sink stands in for alertstore's agent-run callback, so the
// adapter and the prompt can be exercised before either is pointed at a real
// deployment.
//
// It accepts any PATCH, prints what arrived, and always answers 200 — but it
// also applies the checks alertstore would, and says so when a report would
// have been refused. A prompt that teaches an agent the wrong vocabulary is
// much cheaper to find here than as a 400 from a live service.
package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"time"
)

// Mirrors alertstore's agent.ValidVerdict and agent.ValidStatus. Duplicated on
// purpose: this is a test double, and a double that imports the thing it is
// standing in for stops being able to catch the thing drifting.
var (
	verdicts = []string{"noise", "self_recovered", "mitigated", "escalation", "unknown"}
	statuses = []string{"running", "succeeded", "failed"}
)

const maxSummaryBytes = 2000

func main() {
	addr := flag.String("addr", ":8898", "listen address")
	flag.Parse()

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		body, _ := io.ReadAll(io.LimitReader(r.Body, 1<<20))

		fmt.Printf("\n─── %s  %s %s\n", time.Now().Format(time.RFC3339), r.Method, r.URL.Path)
		fmt.Printf("auth: %s\n", orNone(r.Header.Get("Authorization")))

		var report struct {
			Status     string          `json:"status"`
			Verdict    string          `json:"verdict"`
			Summary    string          `json:"summary"`
			SessionRef string          `json:"session_ref"`
			Detail     json.RawMessage `json:"detail"`
		}
		if err := json.Unmarshal(body, &report); err != nil {
			fmt.Println(string(body))
		} else {
			pretty, _ := json.MarshalIndent(json.RawMessage(body), "", "  ")
			fmt.Println(string(pretty))
			for _, complaint := range check(report.Status, report.Verdict, report.Summary) {
				fmt.Printf("!! %s\n", complaint)
			}
		}

		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"status":"ok"}`))
	})

	fmt.Fprintf(os.Stderr, "callback sink on %s — waiting for agent reports\n", *addr)
	if err := http.ListenAndServe(*addr, nil); err != nil {
		fmt.Fprintf(os.Stderr, "sink error: %s\n", err)
		os.Exit(1)
	}
}

func check(status, verdict, summary string) []string {
	var complaints []string
	if status != "" && !contains(statuses, status) {
		complaints = append(complaints,
			fmt.Sprintf("alertstore would reject status %q (want one of %s)", status, strings.Join(statuses, ", ")))
	}
	if verdict != "" && !contains(verdicts, verdict) {
		complaints = append(complaints,
			fmt.Sprintf("alertstore would reject verdict %q (want one of %s)", verdict, strings.Join(verdicts, ", ")))
	}
	if n := len(summary); n > maxSummaryBytes {
		complaints = append(complaints,
			fmt.Sprintf("summary is %d bytes; alertstore truncates at %d", n, maxSummaryBytes))
	}
	return complaints
}

func contains(haystack []string, needle string) bool {
	for _, v := range haystack {
		if v == needle {
			return true
		}
	}
	return false
}

func orNone(s string) string {
	if s == "" {
		return "(none)"
	}
	return s
}
