package registry

import (
	"context"
	"encoding/json"
	"testing"

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

func mustConfig(t *testing.T, c Config) json.RawMessage {
	t.Helper()
	b, err := json.Marshal(c)
	if err != nil {
		t.Fatalf("marshal config: %v", err)
	}
	return b
}

func TestValidate(t *testing.T) {
	tr := &trigger{}
	cases := []struct {
		name    string
		cfg     json.RawMessage
		wantErr bool
	}{
		{"empty body rejected", nil, true},
		{"missing image rejected", mustConfig(t, Config{TagPattern: "*"}), true},
		{"valid wildcard", mustConfig(t, Config{Image: "owner/app", TagPattern: "*"}), false},
		{"valid with glob", mustConfig(t, Config{Image: "registry.example.com/owner/app", TagPattern: "v*"}), false},
		{"invalid glob", mustConfig(t, Config{Image: "owner/app", TagPattern: "v[oops"}), true},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			err := tr.Validate(tc.cfg)
			if (err != nil) != tc.wantErr {
				t.Fatalf("Validate(%s) err=%v want err=%v", tc.name, err, tc.wantErr)
			}
		})
	}
}

func TestImageMatches(t *testing.T) {
	cases := []struct {
		name        string
		want, got   string
		shouldMatch bool
	}{
		{"exact qualified", "registry.example.com/owner/app", "registry.example.com/owner/app", true},
		{"host case-insensitive", "REGISTRY.example.com/owner/app", "registry.example.com/owner/app", true},
		{"path mismatch", "registry.example.com/owner/app", "registry.example.com/owner/other", false},
		{"different registry", "a.example.com/owner/app", "b.example.com/owner/app", false},
		// Single-segment images (Docker Hub officials) — recently fixed.
		{"both single-segment equal", "nginx", "nginx", true},
		{"both single-segment unequal", "nginx", "postgres", false},
		{"want single, got qualified", "nginx", "library/nginx", false},
		{"want qualified, got single", "library/nginx", "nginx", false},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			if got := imageMatches(tc.want, tc.got); got != tc.shouldMatch {
				t.Errorf("imageMatches(%q, %q) = %v, want %v", tc.want, tc.got, got, tc.shouldMatch)
			}
		})
	}
}

func TestMatch(t *testing.T) {
	tr := &trigger{}
	cfg := mustConfig(t, Config{Image: "registry.example.com/owner/app", TagPattern: "v*"})
	wl := plugin.Workload{
		ID:            "wkl-1",
		TriggerConfig: cfg,
	}

	t.Run("wrong event kind returns nil", func(t *testing.T) {
		evt := plugin.InboundEvent{Kind: "git-push"}
		intent, err := tr.Match(context.Background(), plugin.Deps{}, wl, evt)
		if err != nil || intent != nil {
			t.Fatalf("expected nil intent, got intent=%v err=%v", intent, err)
		}
	})

	t.Run("matching push produces intent", func(t *testing.T) {
		evt := plugin.InboundEvent{
			Kind: "image-push",
			Image: &plugin.ImagePushEvent{
				Registry: "registry.example.com",
				Repo:     "owner/app",
				Tag:      "v1.2.3",
			},
		}
		intent, err := tr.Match(context.Background(), plugin.Deps{}, wl, evt)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if intent == nil {
			t.Fatal("expected non-nil intent")
		}
		if intent.Reference != "v1.2.3" {
			t.Errorf("intent.Reference = %q, want v1.2.3", intent.Reference)
		}
		if intent.Reason != "registry-push" {
			t.Errorf("intent.Reason = %q, want registry-push", intent.Reason)
		}
	})

	t.Run("tag outside glob returns nil", func(t *testing.T) {
		evt := plugin.InboundEvent{
			Kind: "image-push",
			Image: &plugin.ImagePushEvent{
				Registry: "registry.example.com",
				Repo:     "owner/app",
				Tag:      "latest", // doesn't match v*
			},
		}
		intent, err := tr.Match(context.Background(), plugin.Deps{}, wl, evt)
		if err != nil || intent != nil {
			t.Fatalf("expected nil intent for tag=latest, got intent=%v err=%v", intent, err)
		}
	})

	t.Run("wrong repo returns nil", func(t *testing.T) {
		evt := plugin.InboundEvent{
			Kind: "image-push",
			Image: &plugin.ImagePushEvent{
				Registry: "registry.example.com",
				Repo:     "owner/other",
				Tag:      "v1.0.0",
			},
		}
		intent, err := tr.Match(context.Background(), plugin.Deps{}, wl, evt)
		if err != nil || intent != nil {
			t.Fatalf("expected nil intent for wrong repo, got intent=%v err=%v", intent, err)
		}
	})

	t.Run("empty pattern matches anything", func(t *testing.T) {
		wlAny := plugin.Workload{
			ID:            "wkl-any",
			TriggerConfig: mustConfig(t, Config{Image: "owner/app"}),
		}
		evt := plugin.InboundEvent{
			Kind: "image-push",
			Image: &plugin.ImagePushEvent{Repo: "owner/app", Tag: "latest"},
		}
		intent, err := tr.Match(context.Background(), plugin.Deps{}, wlAny, evt)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if intent == nil {
			t.Fatal("expected match with empty pattern")
		}
	})
}
