package service_test

import (
	"context"
	"fmt"
	"strings"
	"testing"

	"github.com/pingcap/agent-git-service/internal/db"
	"github.com/pingcap/agent-git-service/internal/service"
)

func TestImportIdentitySameLoginStaysPlaceholderUntilAccepted(t *testing.T) {
	svc, ctx, cleanup := setupImportTestService(t)
	defer cleanup()
	job := createImportBatchTestJob(t, svc, ctx)

	// A same-name AGS account is deliberately not evidence that it owns this
	// GitHub identity.
	existing := db.User{Login: "alice", Name: "Alice", Type: db.TypeUser, UserKind: db.UserKindHuman, Status: db.UserStatusActive}
	if err := svc.DBForCtx(ctx).Create(&existing).Error; err != nil {
		t.Fatalf("create existing AGS user: %v", err)
	}
	mustAddOrgMember(t, svc, ctx, targetOrganizationID(t, svc, ctx, job), existing.ID, db.OrganizationRoleMember)

	stageImportedIssue(t, svc, ctx, job, 101, "alice")
	if _, err := svc.ApplyImportBatches(ctx, job.ID); err != nil {
		t.Fatalf("ApplyImportBatches: %v", err)
	}

	source := importedSourceUser(t, svc, ctx, job.ID, "alice")
	if source.TargetUserID != nil || source.State != "pending_claim" {
		t.Fatalf("same-login identity was auto-linked: %#v", source)
	}
	if !strings.HasPrefix(source.PlaceholderLogin, "imported-github-") {
		t.Fatalf("placeholder login = %q", source.PlaceholderLogin)
	}
	var issue db.Issue
	if err := svc.DBForCtx(ctx).Preload("Author").Take(&issue, "repository_id = ? AND number = ?", *job.TargetRepoID, 1).Error; err != nil {
		t.Fatalf("read imported issue: %v", err)
	}
	if issue.Author.Login != source.PlaceholderLogin {
		t.Fatalf("issue author = %q, want placeholder %q", issue.Author.Login, source.PlaceholderLogin)
	}

	mapped, err := svc.MapImportSourceUsers(ctx, job.ID, []service.ImportSourceUserMappingInput{{SourceUserID: source.ID, TargetUserLogin: existing.Login}})
	if err != nil {
		t.Fatalf("MapImportSourceUsers: %v", err)
	}
	if len(mapped) != 1 || mapped[0].InvitationState != "pending" || mapped[0].TargetUserID != nil {
		t.Fatalf("mapping should be a pending invitation: %#v", mapped)
	}
	if _, err := svc.RespondToImportAttributionInvitation(ctx, job.ID, source.ID, existing.ID, true); err != nil {
		t.Fatalf("RespondToImportAttributionInvitation: %v", err)
	}
	if err := svc.DBForCtx(ctx).Preload("Author").Take(&issue, "repository_id = ? AND number = ?", *job.TargetRepoID, 1).Error; err != nil {
		t.Fatalf("read reattributed issue: %v", err)
	}
	if issue.Author.Login != existing.Login {
		t.Fatalf("issue author = %q, want %q after acceptance", issue.Author.Login, existing.Login)
	}
}

func TestImportIdentityVerifiedGitHubIDAutoLinks(t *testing.T) {
	svc, ctx, cleanup := setupImportTestService(t)
	defer cleanup()
	job := createImportBatchTestJob(t, svc, ctx)

	member := db.User{Login: "not-the-github-login", Name: "Member", Type: db.TypeUser, UserKind: db.UserKindHuman, Status: db.UserStatusActive}
	if err := svc.DBForCtx(ctx).Create(&member).Error; err != nil {
		t.Fatalf("create member: %v", err)
	}
	mustAddOrgMember(t, svc, ctx, targetOrganizationID(t, svc, ctx, job), member.ID, db.OrganizationRoleMember)
	if err := svc.DBForCtx(ctx).Create(&db.UserIdentity{UserID: member.ID, Provider: "github", Subject: "202"}).Error; err != nil {
		t.Fatalf("create verified GitHub identity: %v", err)
	}

	stageImportedIssue(t, svc, ctx, job, 202, "github-alice")
	if _, err := svc.ApplyImportBatches(ctx, job.ID); err != nil {
		t.Fatalf("ApplyImportBatches: %v", err)
	}

	source := importedSourceUser(t, svc, ctx, job.ID, "github-alice")
	if source.TargetUserID == nil || *source.TargetUserID != member.ID || source.State != "claimed" {
		t.Fatalf("verified GitHub identity was not auto-linked: %#v", source)
	}
	var issue db.Issue
	if err := svc.DBForCtx(ctx).Preload("Author").Take(&issue, "repository_id = ? AND number = ?", *job.TargetRepoID, 1).Error; err != nil {
		t.Fatalf("read imported issue: %v", err)
	}
	if issue.Author.Login != member.Login {
		t.Fatalf("issue author = %q, want verified member %q", issue.Author.Login, member.Login)
	}
}

func TestImportIdentityVerifiedGitHubIDOutsideTargetOrgStaysPlaceholder(t *testing.T) {
	svc, ctx, cleanup := setupImportTestService(t)
	defer cleanup()
	job := createImportBatchTestJob(t, svc, ctx)

	memberElsewhere := db.User{Login: "elsewhere", Name: "Elsewhere", Type: db.TypeUser, UserKind: db.UserKindHuman, Status: db.UserStatusActive}
	if err := svc.DBForCtx(ctx).Create(&memberElsewhere).Error; err != nil {
		t.Fatalf("create user: %v", err)
	}
	if err := svc.DBForCtx(ctx).Create(&db.UserIdentity{UserID: memberElsewhere.ID, Provider: "github", Subject: "303"}).Error; err != nil {
		t.Fatalf("create verified GitHub identity: %v", err)
	}

	stageImportedIssue(t, svc, ctx, job, 303, "github-outside")
	if _, err := svc.ApplyImportBatches(ctx, job.ID); err != nil {
		t.Fatalf("ApplyImportBatches: %v", err)
	}
	source := importedSourceUser(t, svc, ctx, job.ID, "github-outside")
	if source.TargetUserID != nil || source.State != "pending_claim" {
		t.Fatalf("outside-org identity was auto-linked: %#v", source)
	}
}

func stageImportedIssue(t *testing.T, svc *service.Service, ctx context.Context, job db.ImportJob, githubID int, githubLogin string) {
	t.Helper()
	payload := fmt.Sprintf(`{"id":1,"number":1,"title":"test","state":"open","user":{"id":%d,"login":%q,"type":"User"}}`, githubID, githubLogin)
	if _, err := svc.CreateImportBatch(ctx, service.CreateImportBatchInput{
		ImportJobID: job.ID,
		BatchID:     "issues-" + githubLogin,
		ObjectType:  "issues",
		Items: []service.ImportBatchItemInput{{
			SourceID:     "issue-" + githubLogin,
			SourceNumber: intPtr(1),
			PayloadJSON:  payload,
		}},
	}); err != nil {
		t.Fatalf("stage imported issue: %v", err)
	}
}

func importedSourceUser(t *testing.T, svc *service.Service, ctx context.Context, jobID uint, login string) db.ImportSourceUser {
	t.Helper()
	var source db.ImportSourceUser
	if err := svc.DBForCtx(ctx).Take(&source, "import_job_id = ? AND source_login = ?", jobID, login).Error; err != nil {
		t.Fatalf("read imported source user %q: %v", login, err)
	}
	return source
}

func targetOrganizationID(t *testing.T, svc *service.Service, ctx context.Context, job db.ImportJob) uint {
	t.Helper()
	var repo db.Repository
	if err := svc.DBForCtx(ctx).Take(&repo, "id = ?", *job.TargetRepoID).Error; err != nil {
		t.Fatalf("read target repo: %v", err)
	}
	return repo.OwnerID
}
