package plugin

import (
	"encoding/json"
	"testing"

	"github.com/alexei/tinyforge/internal/store"
)

// TestWorkloadFromStore_MapsEveryField pins the full field-for-field contract
// of the consolidated converter. The chief risk of extracting the three former
// per-package copies into one shared function is a silently dropped field —
// especially the three secrets (json:"-", so serialization-based tests can
// never catch a regression here) and the GroupID<-AppID rename.
func TestWorkloadFromStore_MapsEveryField(t *testing.T) {
	faces := []PublicFace{
		{Subdomain: "app", Domain: "example.com", TargetService: "web", TargetPort: 8080, AccessListID: 3, EnableSSL: true},
		{Subdomain: "api", Domain: "example.com", TargetPort: 9090},
	}
	facesJSON, err := json.Marshal(faces)
	if err != nil {
		t.Fatalf("marshal faces: %v", err)
	}

	src := store.Workload{
		ID:                      "wl-1",
		Name:                    "my-workload",
		AppID:                   "grp-7",
		SourceKind:              "dockerfile",
		SourceConfig:            `{"repo":"x"}`,
		TriggerKind:             "git",
		TriggerConfig:           `{"branch":"main"}`,
		PublicFaces:             string(facesJSON),
		ParentWorkloadID:        "parent-2",
		NotificationURL:         "https://hooks.example.com/notify",
		NotificationSecret:      "notif-secret",
		WebhookSecret:           "wh-secret",
		WebhookSigningSecret:    "wh-signing-secret",
		WebhookRequireSignature: true,
		CreatedAt:               "2026-01-01T00:00:00Z",
		UpdatedAt:               "2026-01-02T00:00:00Z",
	}

	got := WorkloadFromStore(src)

	if got.ID != src.ID {
		t.Errorf("ID = %q, want %q", got.ID, src.ID)
	}
	if got.Name != src.Name {
		t.Errorf("Name = %q, want %q", got.Name, src.Name)
	}
	if got.GroupID != src.AppID {
		t.Errorf("GroupID = %q, want AppID %q", got.GroupID, src.AppID)
	}
	if got.ParentWorkloadID != src.ParentWorkloadID {
		t.Errorf("ParentWorkloadID = %q, want %q", got.ParentWorkloadID, src.ParentWorkloadID)
	}
	if got.SourceKind != src.SourceKind {
		t.Errorf("SourceKind = %q, want %q", got.SourceKind, src.SourceKind)
	}
	if string(got.SourceConfig) != src.SourceConfig {
		t.Errorf("SourceConfig = %q, want %q", string(got.SourceConfig), src.SourceConfig)
	}
	if got.TriggerKind != src.TriggerKind {
		t.Errorf("TriggerKind = %q, want %q", got.TriggerKind, src.TriggerKind)
	}
	if string(got.TriggerConfig) != src.TriggerConfig {
		t.Errorf("TriggerConfig = %q, want %q", string(got.TriggerConfig), src.TriggerConfig)
	}
	if got.NotificationURL != src.NotificationURL {
		t.Errorf("NotificationURL = %q, want %q", got.NotificationURL, src.NotificationURL)
	}
	if got.NotificationSecret != src.NotificationSecret {
		t.Errorf("NotificationSecret not carried through")
	}
	if got.WebhookSecret != src.WebhookSecret {
		t.Errorf("WebhookSecret not carried through")
	}
	if got.WebhookSigningSecret != src.WebhookSigningSecret {
		t.Errorf("WebhookSigningSecret not carried through")
	}
	if got.WebhookRequireSignature != src.WebhookRequireSignature {
		t.Errorf("WebhookRequireSignature = %v, want %v", got.WebhookRequireSignature, src.WebhookRequireSignature)
	}
	if got.CreatedAt != src.CreatedAt {
		t.Errorf("CreatedAt = %q, want %q", got.CreatedAt, src.CreatedAt)
	}
	if got.UpdatedAt != src.UpdatedAt {
		t.Errorf("UpdatedAt = %q, want %q", got.UpdatedAt, src.UpdatedAt)
	}

	if len(got.PublicFaces) != len(faces) {
		t.Fatalf("PublicFaces len = %d, want %d", len(got.PublicFaces), len(faces))
	}
	if got.PublicFaces[0] != faces[0] || got.PublicFaces[1] != faces[1] {
		t.Errorf("PublicFaces = %+v, want %+v", got.PublicFaces, faces)
	}
}

// TestWorkloadFromStore_PublicFaces covers the PublicFaces decode branch,
// including the malformed-JSON path that the consolidation newly unified onto
// "log and treat as empty" for every caller (the old reconciler/webhook copies
// silently swallowed the error). A decode failure must never fail the
// conversion or panic — it yields nil faces.
func TestWorkloadFromStore_PublicFaces(t *testing.T) {
	tests := []struct {
		name    string
		raw     string
		wantLen int
		wantNil bool
	}{
		{name: "empty string yields nil", raw: "", wantLen: 0, wantNil: true},
		{name: "empty array yields empty", raw: "[]", wantLen: 0, wantNil: false},
		{name: "malformed json yields nil", raw: "{not-json", wantLen: 0, wantNil: true},
		{name: "wrong-shape json yields nil", raw: `{"a":1}`, wantLen: 0, wantNil: true},
		{name: "single valid face", raw: `[{"Subdomain":"a"}]`, wantLen: 1, wantNil: false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := WorkloadFromStore(store.Workload{ID: "wl", PublicFaces: tt.raw})
			if len(got.PublicFaces) != tt.wantLen {
				t.Errorf("PublicFaces len = %d, want %d", len(got.PublicFaces), tt.wantLen)
			}
			if tt.wantNil && got.PublicFaces != nil {
				t.Errorf("PublicFaces = %+v, want nil", got.PublicFaces)
			}
		})
	}
}
