package api

import (
	"encoding/json"
	"testing"
)

func TestNormalizeGreencellTLS(t *testing.T) {
	for _, in := range []string{"on", "ON", "true", "yes", "1", " tls ", "mqtts"} {
		if got := normalizeGreencellTLS(in); got != "on" {
			t.Errorf("normalizeGreencellTLS(%q) = %q, want on", in, got)
		}
	}
	for _, in := range []string{"off", "FALSE", "no", "0", "plain"} {
		if got := normalizeGreencellTLS(in); got != "off" {
			t.Errorf("normalizeGreencellTLS(%q) = %q, want off", in, got)
		}
	}
	// Unset and unrecognized both resolve to "", which lets the cascade fall
	// through to the next layer rather than pinning it to a guess.
	for _, in := range []string{"", "   ", "maybe"} {
		if got := normalizeGreencellTLS(in); got != "" {
			t.Errorf("normalizeGreencellTLS(%q) = %q, want empty", in, got)
		}
	}
}

func TestNormalizeGreencellPort(t *testing.T) {
	cases := map[string]string{
		"1883": "1883", " 8883 ": "8883", "1": "1", "65535": "65535",
		"": "", "0": "", "-1": "", "65536": "", "abc": "", "18 83": "",
	}
	for in, want := range cases {
		if got := normalizeGreencellPort(in); got != want {
			t.Errorf("normalizeGreencellPort(%q) = %q, want %q", in, got, want)
		}
	}
}

func TestNormalizeGreencellTimeout(t *testing.T) {
	cases := map[string]string{
		"12": "12", " 60 ": "60", "1": "1",
		"": "", "0": "", "61": "", "-5": "", "abc": "",
	}
	for in, want := range cases {
		if got := normalizeGreencellTimeout(in); got != want {
			t.Errorf("normalizeGreencellTimeout(%q) = %q, want %q", in, got, want)
		}
	}
}

func TestGreencellLockedFor(t *testing.T) {
	// A value set above the caller's editable layer is locked to them.
	if !greencellLockedFor("global", "user") {
		t.Error("a global value should lock the user layer")
	}
	if !greencellLockedFor("org", "user") {
		t.Error("an org value should lock the user layer")
	}
	if greencellLockedFor("user", "user") {
		t.Error("a caller's own layer is never locked to them")
	}
	if greencellLockedFor("org", "org") {
		t.Error("an org admin may edit the org layer")
	}
	if !greencellLockedFor("global", "org") {
		t.Error("a global value should lock the org layer")
	}
	// Unset is editable: the caller may be the first to set it.
	if greencellLockedFor("unset", "user") {
		t.Error("an unset field should be editable")
	}
	// A superadmin manages the global layer in the panel, not here.
	if !greencellLockedFor("unset", "none") {
		t.Error("the read-only scope locks everything")
	}
}

func TestGreencellMaskPresent(t *testing.T) {
	if got := greencellMaskPresent("hunter2"); got != greencellSecretMask {
		t.Errorf("a present secret should mask, got %q", got)
	}
	for _, in := range []string{"", "   "} {
		if got := greencellMaskPresent(in); got != "" {
			t.Errorf("an absent secret should stay empty, got %q", got)
		}
	}
}

func TestMergeGreencellPreservesOtherPlugins(t *testing.T) {
	existing := json.RawMessage(`{"toyota":{"enabled":true},"ankerSolix":{"enabled":true}}`)
	out := mergeGreencell(existing, func(gs *greencellStored) {
		gs.Enabled = true
		gs.Config.Host = "10.2.1.10"
	})

	var doc map[string]json.RawMessage
	if err := json.Unmarshal(out, &doc); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}
	for _, key := range []string{"toyota", "ankerSolix", "greencell"} {
		if _, ok := doc[key]; !ok {
			t.Errorf("key %q should survive the merge", key)
		}
	}
	var gs greencellStored
	if err := json.Unmarshal(doc["greencell"], &gs); err != nil {
		t.Fatalf("unmarshal greencell: %v", err)
	}
	if !gs.Enabled || gs.Config.Host != "10.2.1.10" {
		t.Errorf("merged entry = %+v", gs)
	}
}

func TestMergeGreencellHandlesEmptyAndNullBlobs(t *testing.T) {
	for _, existing := range []json.RawMessage{nil, json.RawMessage(`null`), json.RawMessage(``)} {
		out := mergeGreencell(existing, func(gs *greencellStored) { gs.Config.Serial = "SN1" })
		var doc greencellSettingsDoc
		if err := json.Unmarshal(out, &doc); err != nil {
			t.Fatalf("unmarshal %q: %v", existing, err)
		}
		if doc.Greencell.Config.Serial != "SN1" {
			t.Errorf("blob %q merged to %+v", existing, doc.Greencell)
		}
	}
}

func TestMergeGreencellKeepsUntouchedFields(t *testing.T) {
	// Toggling the enable flag must not wipe the stored broker config.
	first := mergeGreencell(nil, func(gs *greencellStored) {
		gs.Config = greencellConfig{Host: "mqtt.local", Port: "1883", Password: "secret"}
	})
	second := mergeGreencell(first, func(gs *greencellStored) { gs.Enabled = true })

	var doc greencellSettingsDoc
	if err := json.Unmarshal(second, &doc); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}
	if doc.Greencell.Config.Host != "mqtt.local" || doc.Greencell.Config.Password != "secret" {
		t.Errorf("config lost across the merge: %+v", doc.Greencell.Config)
	}
	if !doc.Greencell.Enabled {
		t.Error("enable flag should have been set")
	}
}

func TestGreencellPluginConfigCarriesEveryField(t *testing.T) {
	res := greencellResolution{eff: greencellConfig{
		Host: "10.2.1.10", Port: "8883", TLS: "on",
		Username: "dv", Password: "secret", Serial: "SN1",
		CommandTopic: "/greencell/evse/{sn}/command", Timeout: "20",
	}}
	cfg := greencellPluginConfig(res)
	want := map[string]string{
		"host": "10.2.1.10", "port": "8883", "tls": "on",
		"username": "dv", "password": "secret", "serial": "SN1",
		"commandTopic": "/greencell/evse/{sn}/command", "timeout": "20",
	}
	for k, v := range want {
		if cfg[k] != v {
			t.Errorf("config[%q] = %q, want %q", k, cfg[k], v)
		}
	}
	if len(cfg) != len(want) {
		t.Errorf("config carries %d keys, want %d: %v", len(cfg), len(want), cfg)
	}
}

func TestGreencellGate(t *testing.T) {
	full := greencellResolution{
		available: true, orgEnabled: true, enabled: true,
		eff: greencellConfig{Host: "mqtt.local"},
	}
	if reason := greencellGate(full, true); reason != "" {
		t.Errorf("a fully-configured integration should pass, got %q", reason)
	}

	cases := []struct {
		name string
		res  greencellResolution
	}{
		{"master switch off", greencellResolution{orgEnabled: true, enabled: true, eff: greencellConfig{Host: "h"}}},
		{"org gate off", greencellResolution{available: true, enabled: true, eff: greencellConfig{Host: "h"}}},
		{"no broker", greencellResolution{available: true, orgEnabled: true, enabled: true}},
	}
	for _, tc := range cases {
		if reason := greencellGate(tc.res, true); reason == "" {
			t.Errorf("%s should have been gated", tc.name)
		}
	}

	// The personal opt-in gates data reads but not a probe: the probe is how you
	// check the settings before turning the integration on.
	noOptIn := greencellResolution{available: true, orgEnabled: true, eff: greencellConfig{Host: "h"}}
	if reason := greencellGate(noOptIn, true); reason == "" {
		t.Error("a data read requires the personal opt-in")
	}
	if reason := greencellGate(noOptIn, false); reason != "" {
		t.Errorf("a probe should not require the opt-in, got %q", reason)
	}
}
