// Command fakefcmd is a test double for the FCM HTTP v1 API. It
// accepts POST /v1/projects/.../messages:send and acks 200 with a
// fake message name. Used by M1 to prove the round-trip without
// burning FCM credits or requiring a Google service account.
//
// In M3 we replace this with a real FCM client in internal/fcm/.
//
// Run:
//
//	fakefcmd --addr :8820
//
// The deliverd-fcm worker points at this via env var
// BA_FAKECMD_URL (or just hardcoded for M1).
package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log/slog"
	"net/http"
	"os"
	"sync/atomic"
	"time"
)

func main() {
	var (
		addr     = flag.String("addr", ":8820", "HTTP listen addr")
		failRate = flag.Float64("fail-rate", 0, "fraction of requests to fail (0-1) for fault injection")
	)
	flag.Parse()
	logger := slog.New(slog.NewTextHandler(os.Stderr, nil))

	var (
		received  atomic.Uint64
		failed    atomic.Uint64
		failMode  atomic.Int32 // 0 = healthy, 1 = fail every request
	)

	mux := http.NewServeMux()
	mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(map[string]any{
			"status":   "ok",
			"service":  "fakefcmd",
			"received": received.Load(),
			"failed":   failed.Load(),
			"fail_mode": failMode.Load() == 1,
		})
	})

	// M8: runtime failure control. The smoke test flips
	// this to 1 to force all sends into the DLQ, then
	// back to 0 to allow a successful replay. This
	// avoids restarting the container for the smoke.
	mux.HandleFunc("/control", func(w http.ResponseWriter, r *http.Request) {
		v := r.URL.Query().Get("fail")
		switch v {
		case "1", "true":
			failMode.Store(1)
			logger.Warn("fakefcmd: fail mode ON")
		case "0", "false":
			failMode.Store(0)
			logger.Info("fakefcmd: fail mode OFF")
		default:
			http.Error(w, "pass ?fail=0 or ?fail=1", http.StatusBadRequest)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(map[string]any{
			"fail_mode": failMode.Load() == 1,
		})
	})

	mux.HandleFunc("/v1/projects/", func(w http.ResponseWriter, r *http.Request) {
		received.Add(1)
		body, _ := io.ReadAll(r.Body)
		_ = r.Body.Close()
		// M8: runtime fail mode wins over fail-rate.
		if failMode.Load() == 1 {
			failed.Add(1)
			logger.Warn("fakefcmd fault injection (runtime)", "path", r.URL.Path)
			w.WriteHeader(http.StatusServiceUnavailable)
			_, _ = w.Write([]byte(`{"error":{"status":"UNAVAILABLE","message":"fakefcmd runtime fail"}}`))
			return
		}
		if *failRate > 0 && (float64(received.Load())*1.0/100.0) < *failRate {
			// crude: 1 in N fails. good enough for fault-injection tests.
			failed.Add(1)
			logger.Warn("fakefcmd fault injection", "path", r.URL.Path)
			w.WriteHeader(http.StatusServiceUnavailable)
			_, _ = w.Write([]byte(`{"error":{"status":"UNAVAILABLE","message":"fakefcmd fault inject"}}`))
			return
		}
		// Log a compact view (don't print full payload; can be fat).
		var msg struct {
			Message struct {
				Token        string            `json:"token"`
				Notification struct{ Title, Body string } `json:"notification"`
				Data         map[string]string `json:"data"`
			} `json:"message"`
		}
		_ = json.Unmarshal(body, &msg)
		logger.Info("fakefcmd send",
			"path", r.URL.Path,
			"token", truncate(msg.Message.Token, 32),
			"title", truncate(msg.Message.Notification.Title, 80),
			"data_keys", len(msg.Message.Data),
			"alert_id", msg.Message.Data["alert_id"],
		)
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(map[string]string{
			"name": fmt.Sprintf("projects/fakefcmd/messages/%d", time.Now().UnixNano()),
		})
	})

	logger.Info("fakefcmd listening", "addr", *addr)
	srv := &http.Server{
		Addr:              *addr,
		Handler:           mux,
		ReadHeaderTimeout: 5 * time.Second,
	}
	if err := srv.ListenAndServe(); err != nil {
		logger.Error("fakefcmd", "err", err)
		os.Exit(1)
	}
}

func truncate(s string, n int) string {
	if len(s) <= n {
		return s
	}
	return s[:n] + "…"
}
