package git

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 mode rejected", mustConfig(t, Config{Repo: "owner/repo"}), true},
		{"push mode valid", mustConfig(t, Config{Repo: "owner/repo", Mode: "push", Branch: "main"}), false},
		{"push mode without branch (any-branch)", mustConfig(t, Config{Repo: "owner/repo", Mode: "push"}), false},
		{"tag mode valid", mustConfig(t, Config{Repo: "owner/repo", Mode: "tag", TagPattern: "v*"}), false},
		{"tag mode no pattern (wildcard fallback)", mustConfig(t, Config{Repo: "owner/repo", Mode: "tag"}), false},
		{"tag mode bad glob", mustConfig(t, Config{Repo: "owner/repo", Mode: "tag", TagPattern: "v[oops"}), true},
		{"unknown mode", mustConfig(t, Config{Repo: "owner/repo", Mode: "merge"}), 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 TestRefMatches(t *testing.T) {
	cases := []struct {
		name string
		cfg  Config
		ref  string
		want bool
	}{
		{"push main matches", Config{Mode: "push", Branch: "main"}, "refs/heads/main", true},
		{"push main rejects other branch", Config{Mode: "push", Branch: "main"}, "refs/heads/dev", false},
		{"push tag is rejected in push mode", Config{Mode: "push", Branch: "main"}, "refs/tags/v1.0.0", false},
		{"push any-branch", Config{Mode: "push"}, "refs/heads/whatever", true},
		{"tag mode v* matches v1.2.3", Config{Mode: "tag", TagPattern: "v*"}, "refs/tags/v1.2.3", true},
		{"tag mode v* rejects latest", Config{Mode: "tag", TagPattern: "v*"}, "refs/tags/latest", false},
		{"tag mode rejects heads ref", Config{Mode: "tag", TagPattern: "v*"}, "refs/heads/main", false},
		{"tag mode empty pattern matches any tag", Config{Mode: "tag"}, "refs/tags/whatever", true},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			if got := refMatches(tc.cfg, tc.ref); got != tc.want {
				t.Errorf("refMatches(%+v, %q) = %v, want %v", tc.cfg, tc.ref, got, tc.want)
			}
		})
	}
}

func TestMatch(t *testing.T) {
	tr := &trigger{}
	wl := plugin.Workload{
		ID:            "wkl-1",
		TriggerConfig: mustConfig(t, Config{Repo: "Owner/Repo", Mode: "push", Branch: "main"}),
	}

	t.Run("wrong event kind", func(t *testing.T) {
		evt := plugin.InboundEvent{Kind: "image-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 fires intent with sha", func(t *testing.T) {
		// Branch is populated by the webhook ingress alongside Ref; the
		// trigger reads either independently. Set both here to mirror the
		// real wire shape.
		evt := plugin.InboundEvent{
			Kind: "git-push",
			Git: &plugin.GitEvent{
				Repo:      "owner/repo",
				Ref:       "refs/heads/main",
				Branch:    "main",
				CommitSHA: "deadbeef",
				Pusher:    "alice",
			},
		}
		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 != "deadbeef" {
			t.Errorf("intent.Reference = %q, want deadbeef", intent.Reference)
		}
		if intent.Reason != "git-push" {
			t.Errorf("intent.Reason = %q, want git-push", intent.Reason)
		}
		if intent.Metadata["branch"] != "main" {
			t.Errorf("expected branch=main in metadata, got %q", intent.Metadata["branch"])
		}
	})

	t.Run("repo case-insensitive comparison", func(t *testing.T) {
		evt := plugin.InboundEvent{
			Kind: "git-push",
			Git:  &plugin.GitEvent{Repo: "OWNER/REPO", Ref: "refs/heads/main"},
		}
		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 case-insensitive repo match")
		}
	})

	t.Run("wrong repo returns nil", func(t *testing.T) {
		evt := plugin.InboundEvent{
			Kind: "git-push",
			Git:  &plugin.GitEvent{Repo: "other/repo", Ref: "refs/heads/main"},
		}
		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)
		}
	})
}
