package db

import (
	"context"
	"encoding/json"
	"path/filepath"
	"slices"
	"testing"
	"time"

	"tangled.org/core/api/tangled"
)

func newTestDB(t *testing.T) *DB {
	t.Helper()
	d, err := Make(context.Background(), filepath.Join(t.TempDir(), "spindle.db"))
	if err != nil {
		t.Fatalf("Make: %v", err)
	}
	t.Cleanup(func() { d.Close() })
	return d
}

func seedPipelineEvent(t *testing.T, d *DB, rkey, repoDid, kind string, created int64) {
	t.Helper()
	repo := repoDid
	tm := &tangled.Pipeline_TriggerMetadata{
		Kind: kind,
		Repo: &tangled.Pipeline_TriggerRepo{Knot: "knot.test", RepoDid: &repo, Did: repoDid},
	}
	switch kind {
	case "push":
		tm.Push = &tangled.Pipeline_PushTriggerData{NewSha: "sha-" + rkey, Ref: "refs/heads/main"}
	case "pull_request":
		tm.PullRequest = &tangled.Pipeline_PullRequestTriggerData{SourceSha: "sha-" + rkey, SourceBranch: "feature", TargetBranch: "main"}
	case "manual":
		tm.Manual = &tangled.Pipeline_ManualTriggerData{Sha: "sha-" + rkey}
	}
	raw := tangled.Pipeline{
		TriggerMetadata: tm,
		Workflows:       []*tangled.Pipeline_Workflow{{Name: "ci.yml"}},
	}
	eventJson, err := json.Marshal(raw)
	if err != nil {
		t.Fatalf("marshal pipeline: %v", err)
	}
	if _, err := d.Exec(
		`insert into events (rkey, nsid, event, created) values (?, 'sh.tangled.pipeline', ?, ?)`,
		rkey, string(eventJson), created,
	); err != nil {
		t.Fatalf("seed event %s: %v", rkey, err)
	}
}

func TestQueryPipelines_FilterByKind(t *testing.T) {
	d := newTestDB(t)
	ctx := context.Background()
	repo := "did:plc:boltless"
	base := time.Now().UnixNano()

	seedPipelineEvent(t, d, "p-push", repo, "push", base+1)
	seedPipelineEvent(t, d, "p-pull", repo, "pull_request", base+2)
	seedPipelineEvent(t, d, "p-manual", repo, "manual", base+3)

	cases := []struct {
		kinds     []string
		wantTotal int64
		wantKinds []string
	}{
		{nil, 3, []string{"manual", "pull_request", "push"}},
		{[]string{"push"}, 1, []string{"push"}},
		{[]string{"pull_request"}, 1, []string{"pull_request"}},
		{[]string{"manual"}, 1, []string{"manual"}},
		{[]string{"push", "pull_request"}, 2, []string{"pull_request", "push"}},
	}

	for _, tc := range cases {
		pipelines, _, total, err := d.QueryPipelines(ctx, repo, nil, "", tc.kinds, 30)
		if err != nil {
			t.Fatalf("kinds=%v: QueryPipelines: %v", tc.kinds, err)
		}
		if total != tc.wantTotal {
			t.Errorf("kinds=%v: total = %d, want %d", tc.kinds, total, tc.wantTotal)
		}
		var gotKinds []string
		for _, p := range pipelines {
			gotKinds = append(gotKinds, triggerKindOf(p))
		}
		slices.Sort(gotKinds)
		if !slices.Equal(gotKinds, tc.wantKinds) {
			t.Errorf("kinds=%v: returned %v, want %v", tc.kinds, gotKinds, tc.wantKinds)
		}
	}
}

func TestQueryPipelines_KindScopedToRepo(t *testing.T) {
	d := newTestDB(t)
	ctx := context.Background()
	base := time.Now().UnixNano()

	seedPipelineEvent(t, d, "a-push", "did:plc:alice", "push", base+1)
	seedPipelineEvent(t, d, "b-push", "did:plc:bob", "push", base+2)

	pipelines, _, total, err := d.QueryPipelines(ctx, "did:plc:alice", nil, "", []string{"push"}, 30)
	if err != nil {
		t.Fatalf("QueryPipelines: %v", err)
	}
	if total != 1 || len(pipelines) != 1 {
		t.Fatalf("total=%d len=%d, want exactly alice's single push pipeline", total, len(pipelines))
	}
	if pipelines[0].Repo == nil || *pipelines[0].Repo != "did:plc:alice" {
		t.Errorf("returned pipeline repo = %v, want did:plc:alice", pipelines[0].Repo)
	}
}

func triggerKindOf(p *tangled.CiPipeline) string {
	if p.Trigger == nil {
		return ""
	}
	switch {
	case p.Trigger.CiTrigger_Push != nil:
		return "push"
	case p.Trigger.CiTrigger_PullRequest != nil:
		return "pull_request"
	case p.Trigger.CiTrigger_Manual != nil:
		return "manual"
	}
	return ""
}
