package notify

import (
	"io"
	"log"
	"path/filepath"
	"strings"
	"testing"

	"gitea.dooplex.hu/admin/felhom-hub/internal/store"
)

// R-97c — the operator-only claim must be TRUE, not asserted.
//
// v0.78.0 committed a comment saying a type with no `customerMessages` entry "structurally cannot"
// reach a customer. It can: templates.go falls back to the raw message when the entry is missing, and
// the only customer gate is `prefs.EnabledEvents` — configuration. This suite runs under exactly the
// configuration that would break it.

func opOnlyStore(t *testing.T) *store.Store {
	t.Helper()
	st, err := store.New(filepath.Join(t.TempDir(), "oo.db"), log.New(io.Discard, "", 0))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { st.Close() })
	if err := st.SaveCustomerConfig(&store.CustomerConfig{CustomerID: "c1", APIKey: "k", RetrievalPassword: "p"}); err != nil {
		t.Fatal(err)
	}
	return st
}

// sentTo records every address the dispatcher actually tried to email.
type sentTo struct{ to []string }

func opOnlyDispatcher(t *testing.T, st *store.Store, rec *sentTo) *Dispatcher {
	t.Helper()
	d := NewDispatcher(st, "test-key", "from@felhom.eu", "operator@felhom.eu", true, log.New(io.Discard, "", 0))
	d.sendEmailFn = func(to, subject, body string, headers map[string]string) error {
		rec.to = append(rec.to, to)
		return nil
	}
	return d
}

// SCENARIO E — the breaking configuration: the customer HAS the event enabled and HAS an email.
//
// COMPANION RED-PROOF (observed): delete the operatorOnlyEvents check from processCustomer (the
// v0.78.0 shape) and this fails with
//
//	"R-97c: a customer was emailed an OPERATOR-ONLY event (customer@example.com) — enabled_events
//	 must not be able to opt in"
//
// Restored.
func TestOperatorOnly_CustomerCannotOptIn(t *testing.T) {
	st := opOnlyStore(t)
	// THE BREAKING CONFIG — not today's safe one.
	if err := st.SaveNotificationPrefs("c1", "customer@example.com",
		[]string{"whole_guest_backup_failed", "backup_failed"}, 6); err != nil {
		t.Fatalf("SaveNotificationPrefs: %v", err)
	}

	rec := &sentTo{}
	d := opOnlyDispatcher(t, st, rec)
	d.ProcessEvent("c1", "whole_guest_backup_failed", "error",
		"Whole-guest backup FAILED on the felhom-pbs tier", `{"tier":"felhom-pbs"}`, "controller")

	for _, to := range rec.to {
		if to == "customer@example.com" {
			t.Fatalf("R-97c: a customer was emailed an OPERATOR-ONLY event (%s) — "+
				"enabled_events must not be able to opt in", to)
		}
	}
	// The operator MUST still get it — the guard must not silence the signal entirely.
	gotOperator := false
	for _, to := range rec.to {
		if to == "operator@felhom.eu" {
			gotOperator = true
		}
	}
	if !gotOperator {
		t.Fatal("the operator must still be notified; the guard is customer-only")
	}
}

// The skip must be VISIBLE — a silent drop is indistinguishable from a delivery that never happened.
func TestOperatorOnly_SkipIsLogged(t *testing.T) {
	st := opOnlyStore(t)
	if err := st.SaveNotificationPrefs("c1", "customer@example.com", []string{"whole_guest_backup_failed"}, 6); err != nil {
		t.Fatal(err)
	}
	rec := &sentTo{}
	d := opOnlyDispatcher(t, st, rec)
	d.ProcessEvent("c1", "whole_guest_backup_failed", "error", "boom", `{"tier":"local"}`, "controller")

	logs, err := st.GetRecentNotifications("c1", 20)
	if err != nil {
		t.Fatalf("GetNotificationLog: %v", err)
	}
	found := false
	for _, l := range logs {
		if l.Channel == "customer" && l.Status == "skipped" && strings.Contains(l.ErrorMessage, "operator_only") {
			found = true
		}
	}
	if !found {
		t.Fatalf("the customer skip must be logged as skipped/operator_only so it is visible; got %d row(s)", len(logs))
	}
}

// A NORMAL customer event must be unaffected — the register is narrow, not a blanket mute.
func TestOperatorOnly_NormalCustomerEventStillDelivered(t *testing.T) {
	st := opOnlyStore(t)
	if err := st.SaveNotificationPrefs("c1", "customer@example.com", []string{"backup_failed"}, 6); err != nil {
		t.Fatal(err)
	}
	rec := &sentTo{}
	d := opOnlyDispatcher(t, st, rec)
	d.ProcessEvent("c1", "backup_failed", "error", "app-data backup failed", "{}", "controller")

	got := false
	for _, to := range rec.to {
		if to == "customer@example.com" {
			got = true
		}
	}
	if !got {
		t.Fatal("a normal customer-facing event must still be delivered — the register must not be a blanket mute")
	}
}

// Pin the membership: both R-97a types, by name.
func TestOperatorOnly_RegisterContents(t *testing.T) {
	for _, et := range []string{"whole_guest_backup_failed", "whole_guest_backup_recovered"} {
		if !operatorOnlyEvents[et] {
			t.Fatalf("%s must be operator-only; a customer can take no action on it", et)
		}
	}
	if operatorOnlyEvents["backup_failed"] {
		t.Fatal("backup_failed is the APP-DATA tier and IS customer-facing — do not mute it")
	}
}
