package graphql

import (
	"context"
	"os"
	"testing"

	"github.com/prometheus/client_golang/prometheus/testutil"

	"github.com/pingcap/agent-git-service/internal/db"
	"github.com/pingcap/agent-git-service/internal/embedding"
	"github.com/pingcap/agent-git-service/internal/gitstore"
	"github.com/pingcap/agent-git-service/internal/metrics"
	"github.com/pingcap/agent-git-service/internal/service"
	"github.com/pingcap/agent-git-service/internal/testharness/testdb"
)

func TestRunSearch_DoesNotRecordMetricOnIssueSearchFailure(t *testing.T) {
	server, cleanup := newSearchMetricsTestServer(t)
	defer cleanup()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	before := testutil.ToFloat64(metrics.DefaultPrometheus().SearchTotal)
	conn := server.runSearch(ctx, "repo:tester/missing type:issue", "")
	after := testutil.ToFloat64(metrics.DefaultPrometheus().SearchTotal)

	if after != before {
		t.Fatalf("SearchTotal changed on failed issue search: before=%v after=%v", before, after)
	}
	if got := conn["issueCount"]; got != 0 {
		t.Fatalf("issueCount = %v, want 0", got)
	}
}

func TestRunSearch_DoesNotRecordMetricOnPRSearchFailure(t *testing.T) {
	server, cleanup := newSearchMetricsTestServer(t)
	defer cleanup()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	before := testutil.ToFloat64(metrics.DefaultPrometheus().SearchTotal)
	conn := server.runSearch(ctx, "repo:tester/missing is:pr", "")
	after := testutil.ToFloat64(metrics.DefaultPrometheus().SearchTotal)

	if after != before {
		t.Fatalf("SearchTotal changed on failed PR search: before=%v after=%v", before, after)
	}
	if got := conn["issueCount"]; got != 0 {
		t.Fatalf("issueCount = %v, want 0", got)
	}
}

func TestRunRepoSearch_DoesNotRecordMetricOnFailure(t *testing.T) {
	server, cleanup := newSearchMetricsTestServer(t)
	defer cleanup()

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	before := testutil.ToFloat64(metrics.DefaultPrometheus().SearchTotal)
	conn := server.runRepoSearch(ctx, "user:tester", "")
	after := testutil.ToFloat64(metrics.DefaultPrometheus().SearchTotal)

	if after != before {
		t.Fatalf("SearchTotal changed on failed repo search: before=%v after=%v", before, after)
	}
	if got := conn["repositoryCount"]; got != 0 {
		t.Fatalf("repositoryCount = %v, want 0", got)
	}
}

func TestDoSearch_AliasedSearchRecordsMetricOncePerRequest(t *testing.T) {
	server, cleanup := newSearchMetricsTestServer(t)
	defer cleanup()

	ctx := context.Background()
	user := db.User{Login: "tester", Name: "tester", Type: db.TypeUser}
	if err := server.Svc.DB.Create(&user).Error; err != nil {
		t.Fatalf("create user: %v", err)
	}
	if _, err := server.Svc.CreateRepo(ctx, service.CreateRepoInput{
		OwnerLogin: user.Login,
		Name:       "aliased-search-metrics",
		AutoInit:   true,
	}); err != nil {
		t.Fatalf("create repo: %v", err)
	}
	if _, err := server.Svc.CreateIssue(ctx, service.CreateIssueInput{
		RepoFullName: "tester/aliased-search-metrics",
		Title:        "Aliased search issue",
		Body:         "body",
		AuthorLogin:  user.Login,
	}); err != nil {
		t.Fatalf("create issue: %v", err)
	}

	before := testutil.ToFloat64(metrics.DefaultPrometheus().SearchTotal)
	res := server.doSearch(ctx, gqlRequest{
		Query: `
			query($viewerQuery: String!, $repoQuery: String!) {
				viewerCreated: search(query: $viewerQuery, type: ISSUE, first: 10) { issueCount }
				repoSearch: search(query: $repoQuery, type: ISSUE, first: 10) { issueCount }
			}`,
		Variables: map[string]any{
			"viewerQuery": "repo:tester/aliased-search-metrics author:tester",
			"repoQuery":   "repo:tester/aliased-search-metrics",
		},
	})
	after := testutil.ToFloat64(metrics.DefaultPrometheus().SearchTotal)

	if after != before+1 {
		t.Fatalf("SearchTotal changed by %v, want exactly 1 successful request", after-before)
	}
	data := res["data"].(map[string]any)
	if got := data["viewerCreated"].(map[string]any)["issueCount"]; got != 1 {
		t.Fatalf("viewerCreated issueCount = %v, want 1", got)
	}
	if got := data["repoSearch"].(map[string]any)["issueCount"]; got != 1 {
		t.Fatalf("repoSearch issueCount = %v, want 1", got)
	}
}

func newSearchMetricsTestServer(t *testing.T) (*Server, func()) {
	t.Helper()

	tmpDir, err := os.MkdirTemp("", "graphql-search-metrics-")
	if err != nil {
		t.Fatalf("mkdir temp: %v", err)
	}

	gdb, dbCleanup := testdb.OpenRaw(t, "gql_metrics")
	if err := db.Migrate(gdb); err != nil {
		t.Fatalf("migrate: %v", err)
	}

	store, err := gitstore.New(tmpDir)
	if err != nil {
		t.Fatalf("gitstore.New: %v", err)
	}

	metrics.Init()
	svc := &service.Service{
		DB:       gdb,
		Git:      store,
		BaseURL:  "http://localhost:8080",
		Embedder: embedding.NopEmbedder{},
	}

	cleanup := func() {
		dbCleanup()
		_ = os.RemoveAll(tmpDir)
	}
	return NewServer(svc), cleanup
}
