package image

import (
	"context"
	"strings"
	"testing"
	"time"

	"github.com/alexei/tinyforge/internal/workload/plugin"
)

// TestReconcileIsNoOp locks Fix B: image.Reconcile must do nothing and touch
// neither the Store nor Docker (the generic reconciler pass syncs state). We
// pass a zero-value plugin.Deps whose Store and Docker are nil — the old
// implementation called deps.Store.ListContainersByWorkload then
// deps.Docker.IsContainerRunning, both of which would nil-panic. Returning nil
// without panicking proves it dereferences neither.
func TestReconcileIsNoOp(t *testing.T) {
	src := &source{}
	w := plugin.Workload{ID: "wl-1", Name: "app", SourceKind: "image"}
	if err := src.Reconcile(context.Background(), plugin.Deps{}, w); err != nil {
		t.Fatalf("Reconcile should be a no-op returning nil, got %v", err)
	}
}

func TestBuildContainerName(t *testing.T) {
	ts := time.Unix(1700000000, 0)
	name := buildContainerName("My App", "abcd1234-5678-1234-abcd-deadbeef0000", "v1.2.3", ts)

	if !strings.HasPrefix(name, "tf-My-App-abcd1234-v1.2.3-") {
		t.Errorf("name=%q lost expected prefix", name)
	}
	if strings.Contains(name, " ") {
		t.Errorf("name=%q contains space — sanitizer regressed", name)
	}
	if strings.Contains(name, "/") {
		t.Errorf("name=%q contains slash — sanitizer regressed", name)
	}
	// Suffix is monotonic ms hex — two adjacent timestamps must produce
	// different names so blue-green can run two containers side-by-side.
	other := buildContainerName("My App", "abcd1234-5678-1234-abcd-deadbeef0000", "v1.2.3", ts.Add(time.Millisecond))
	if other == name {
		t.Errorf("expected distinct names across timestamps, got %q twice", name)
	}
}

func TestBuildContainerNameShortID(t *testing.T) {
	// Workload IDs shorter than 8 chars must not panic on slicing.
	name := buildContainerName("app", "ab", "tag", time.Unix(1700000000, 0))
	if !strings.HasPrefix(name, "tf-app-ab-tag-") {
		t.Errorf("unexpected short-ID name: %q", name)
	}
}

func TestFaceEnabled(t *testing.T) {
	cases := []struct {
		face plugin.PublicFace
		want bool
	}{
		{plugin.PublicFace{}, false},
		{plugin.PublicFace{Subdomain: "api"}, true},
		{plugin.PublicFace{Domain: "example.com"}, true},
		{plugin.PublicFace{Subdomain: "api", Domain: "example.com"}, true},
	}
	for i, tc := range cases {
		if got := faceEnabled(tc.face); got != tc.want {
			t.Errorf("case %d face=%+v: got %v want %v", i, tc.face, got, tc.want)
		}
	}
}

func TestFqdnFor(t *testing.T) {
	cases := []struct {
		name   string
		face   plugin.PublicFace
		defDom string
		want   string
	}{
		{"subdomain + face domain", plugin.PublicFace{Subdomain: "api", Domain: "example.com"}, "default.io", "api.example.com"},
		{"subdomain inherits default", plugin.PublicFace{Subdomain: "api"}, "default.io", "api.default.io"},
		{"root domain only", plugin.PublicFace{Domain: "example.com"}, "default.io", "example.com"},
		{"root of default", plugin.PublicFace{}, "default.io", "default.io"},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			if got := fqdnFor(tc.face, tc.defDom); got != tc.want {
				t.Errorf("fqdnFor: got %q, want %q", got, tc.want)
			}
		})
	}
}

func TestPrimaryFace(t *testing.T) {
	t.Run("returns first enabled", func(t *testing.T) {
		faces := []plugin.PublicFace{
			{},                 // disabled
			{Subdomain: "api"}, // first enabled
			{Domain: "second.example.com"},
		}
		got := primaryFace(faces)
		if got.Subdomain != "api" {
			t.Errorf("expected first enabled, got %+v", got)
		}
	})
	t.Run("empty when none enabled", func(t *testing.T) {
		got := primaryFace([]plugin.PublicFace{{}, {}})
		if got.Subdomain != "" || got.Domain != "" {
			t.Errorf("expected zero face, got %+v", got)
		}
	})
}

func TestValidate(t *testing.T) {
	src := &source{}
	cases := []struct {
		name    string
		body    string
		wantErr bool
	}{
		{"empty rejected", "", true},
		{"missing image rejected", `{"port":8080}`, true},
		{"valid minimal", `{"image":"owner/app","port":8080}`, false},
		{"port out of range", `{"image":"x","port":99999}`, true},
		{"volume missing target rejected", `{"image":"x","volumes":[{"source":"/a","scope":"absolute"}]}`, true},
		{"volume missing scope rejected", `{"image":"x","volumes":[{"source":"/a","target":"/b"}]}`, true},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			err := src.Validate([]byte(tc.body))
			if (err != nil) != tc.wantErr {
				t.Fatalf("Validate(%q) err=%v want err=%v", tc.body, err, tc.wantErr)
			}
		})
	}
}
