package monitor

import (
	"strings"
	"testing"

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

// Scenarios D, E and F of the R-260 / G-1 session, asserted on the CONSEQUENCE (does the check
// report degraded, and does it name the right thing) rather than on the mechanism.

func healthyRow() store.HostOOBRow {
	return store.HostOOBRow{
		HostID: "h1", CustomerID: "c1", Present: true,
		FelhomSshdActive: true, Reachable: true, ConfigInvalid: false,
		OperatorPeerConfigured: true,
		OperatorKeyConfigured:  true, OperatorKeyReported: true,
	}
}

// Scenario D — the box this session exists for: everything up, no operator key.
func TestOOBDegraded_NoOperatorKey_IsDegradedAndSaysSo(t *testing.T) {
	r := healthyRow()
	r.OperatorKeyConfigured = false // reported, and false

	if !oobDegraded(r) {
		t.Fatal("a box with felhom-sshd active, reachable, a valid config, a configured peer and NO OPERATOR KEY reported ok — that is R-260, the exact question this check exists to answer")
	}
	reason := oobDegradedReason(r)
	// §7.4: the operator reads this at 07:00 and must know WHICH of the five things is wrong.
	if !strings.Contains(reason, "authorized_key") {
		t.Errorf("the reason must NAME the missing operator key, not just say access is degraded; got %q", reason)
	}
	for _, wrong := range []string{"unreachable", "config invalid"} {
		if strings.Contains(reason, wrong) {
			t.Errorf("the reason names %q, which is not what is wrong: %q", wrong, reason)
		}
	}
}

// Scenario E — a healthy box must be unchanged. No new alert on a box that is fine.
func TestOOBDegraded_HealthyBoxUnchanged(t *testing.T) {
	if oobDegraded(healthyRow()) {
		t.Fatalf("a fully healthy box alerted: %q", oobDegradedReason(healthyRow()))
	}
	if got := oobDegradedReason(healthyRow()); got != "" {
		t.Errorf("healthy box produced a reason %q", got)
	}
}

// Scenario F — an agent too old to report the field. Absence must NOT read as "the key is
// installed". This project has watched an absence read as a fact four times.
func TestOOBDegraded_StanzaWithoutKeyField_IsNotOK(t *testing.T) {
	r := healthyRow()
	r.OperatorKeyReported = false
	r.OperatorKeyConfigured = false // the zero value an absent field leaves behind

	if !oobDegraded(r) {
		t.Fatal("an agent that never said whether the operator key is installed was reported ok — that is the defect returning through the version door")
	}
	reason := oobDegradedReason(r)
	if !strings.Contains(reason, "too old") || !strings.Contains(reason, "UNPROVEN") {
		t.Errorf("the unknown case must be reported DISTINCTLY from a known-missing key, and must not claim the key is absent; got %q", reason)
	}
	// and it must NOT be worded as the known-missing-key case
	if strings.Contains(reason, "nobody can log in") {
		t.Errorf("unknown was reported as if it were a known-missing key: %q", reason)
	}
}

// The peer gate is preserved: a box where OOB was never set up is not "broken", and this session
// must not widen the check beyond the fact that is now arriving.
func TestOOBDegraded_NoOperatorPeer_StillNotEvaluated(t *testing.T) {
	r := healthyRow()
	r.OperatorPeerConfigured = false
	r.OperatorKeyConfigured = false
	r.OperatorKeyReported = true
	if oobDegraded(r) {
		t.Errorf("a box with no operator peer configured must not alert on a missing key: %q", oobDegradedReason(r))
	}
}

// The pre-existing faults must still fire, and still name themselves correctly.
func TestOOBDegraded_ExistingFaultsUnchanged(t *testing.T) {
	for _, tc := range []struct {
		name   string
		mutate func(*store.HostOOBRow)
		expect string
	}{
		{"config invalid", func(r *store.HostOOBRow) { r.ConfigInvalid = true }, "config invalid"},
		{"not active", func(r *store.HostOOBRow) { r.FelhomSshdActive = false }, "not active"},
		{"unreachable", func(r *store.HostOOBRow) { r.Reachable = false }, "unreachable"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			r := healthyRow()
			tc.mutate(&r)
			if !oobDegraded(r) {
				t.Fatalf("%s no longer degrades", tc.name)
			}
			if got := oobDegradedReason(r); !strings.Contains(got, tc.expect) {
				t.Errorf("reason %q does not contain %q", got, tc.expect)
			}
		})
	}
}

// A host with no oob stanza at all is never evaluated — pre-H1 or feature off.
func TestOOBDegraded_AbsentStanzaNeverAlerts(t *testing.T) {
	if oobDegraded(store.HostOOBRow{HostID: "h1"}) {
		t.Error("a host with no oob stanza alerted")
	}
}
