package roleproposal

import (
	"context"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/approval"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

func TestDecideApproveAtomicallyClearsSparseMemberFieldsAndDisablesWake(t *testing.T) {
	t.Parallel()
	ctx := context.Background()
	db := startProposalRollbackDB(t, ctx)
	now := time.Date(2026, 7, 18, 11, 5, 0, 0, time.UTC)
	fixture := seedProposalFixture(t, ctx, db, "approve-sparse-clear", now)
	mustExecProposal(t, ctx, db, `
UPDATE workspace_team_members
SET role_label='Worker label'
WHERE id=$1
`, fixture.WorkerTeamMember)

	service := New(db)
	empty := ""
	wake := false
	submitted, err := service.Submit(ctx, fixture.AuthenticatedLead, SubmitInput{
		TeamID:          fixture.TeamID,
		ClientRequestID: "approve-sparse-clear",
		Description:     proposalString("Updated description"),
		Members: []MemberInput{{
			TeamMemberID:      fixture.WorkerTeamMember,
			RoleLabel:         &empty,
			RoleOverlayPrompt: &empty,
			WakeOnTeamMention: &wake,
		}},
		Now: now.Add(time.Minute),
	})
	require.NoError(t, err)

	result, err := service.Decide(ctx, approval.DecideParams{
		WorkspaceID: fixture.WorkspaceID, ApprovalID: submitted.Approval.ID,
		AssignmentID: submitted.Assignments[0].ID, ActorMemberID: fixture.OwnerMemberID,
		ExpectedVersion: submitted.Approval.Version, ClientDecisionID: "approve-sparse-clear-decision",
		Decision: approval.DecisionApproved, Now: now.Add(2 * time.Minute),
	})
	require.NoError(t, err)
	require.True(t, result.Applied)

	var description string
	require.NoError(t, db.QueryRow(ctx, `
SELECT description FROM workspace_teams WHERE id=$1
`, fixture.TeamID).Scan(&description))
	require.Equal(t, "Updated description", description)
	assertMemberCollaborationState(t, ctx, db, fixture.WorkerTeamMember, "", "", false, 2)
	assertMemberCollaborationState(t, ctx, db, fixture.LeaderTeamMember, "", "Leader overlay", true, 1)
}

func TestDecideApproveRollsBackDescriptionAndPriorMemberWritesWhenLaterMemberCASFails(t *testing.T) {
	t.Parallel()
	ctx := context.Background()
	db := startProposalRollbackDB(t, ctx)
	now := time.Date(2026, 7, 18, 11, 10, 0, 0, time.UTC)
	fixture := seedProposalFixture(t, ctx, db, "approve-cas-rollback", now)
	service := New(db)
	submitted, err := service.Submit(ctx, fixture.AuthenticatedLead, SubmitInput{
		TeamID:          fixture.TeamID,
		ClientRequestID: "approve-cas-rollback",
		Description:     proposalString("Must roll back"),
		Members: []MemberInput{
			{TeamMemberID: fixture.LeaderTeamMember, RoleOverlayPrompt: proposalString("Leader write must roll back")},
			{TeamMemberID: fixture.WorkerTeamMember, RoleOverlayPrompt: proposalString("Worker CAS must fail")},
		},
		Now: now.Add(time.Minute),
	})
	require.NoError(t, err)

	guarded := New(&failNthTeamMemberCASDB{DB: db, failAt: 2})
	_, err = guarded.Decide(ctx, approval.DecideParams{
		WorkspaceID: fixture.WorkspaceID, ApprovalID: submitted.Approval.ID,
		AssignmentID: submitted.Assignments[0].ID, ActorMemberID: fixture.OwnerMemberID,
		ExpectedVersion: submitted.Approval.Version, ClientDecisionID: "approve-cas-rollback-decision",
		Decision: approval.DecisionApproved, Now: now.Add(2 * time.Minute),
	})
	require.ErrorIs(t, err, storeerr.ErrConflict)

	assertTeamState(t, ctx, db, fixture, "Base description", "Leader overlay", 1, "Worker overlay", 1)
	persisted, err := approval.New(db).Load(ctx, fixture.WorkspaceID, submitted.Approval.ID)
	require.NoError(t, err)
	require.Equal(t, approval.StatePending, persisted.State)
	require.Equal(t, submitted.Approval.Version, persisted.Version)
}

func assertMemberCollaborationState(
	t *testing.T,
	ctx context.Context,
	db coredb.QueryRower,
	teamMemberID string,
	wantRoleLabel string,
	wantOverlay string,
	wantWake bool,
	wantOverlayVersion int64,
) {
	t.Helper()
	var roleLabel, overlay string
	var wake bool
	var overlayVersion int64
	require.NoError(t, db.QueryRow(ctx, `
SELECT role_label, role_overlay_prompt, wake_on_team_mention, role_overlay_version
FROM workspace_team_members
WHERE id=$1
`, teamMemberID).Scan(&roleLabel, &overlay, &wake, &overlayVersion))
	require.Equal(t, wantRoleLabel, roleLabel)
	require.Equal(t, wantOverlay, overlay)
	require.Equal(t, wantWake, wake)
	require.Equal(t, wantOverlayVersion, overlayVersion)
}

type failNthTeamMemberCASDB struct {
	coredb.DB
	failAt int
}

func (db *failNthTeamMemberCASDB) BeginTx(
	ctx context.Context,
	options coredb.TxOptions,
) (coredb.Tx, error) {
	tx, err := db.DB.BeginTx(ctx, options)
	if err != nil {
		return nil, err
	}
	return &failNthTeamMemberCASTx{Tx: tx, failAt: db.failAt}, nil
}

type failNthTeamMemberCASTx struct {
	coredb.Tx
	failAt int
	seen   int
}

func (tx *failNthTeamMemberCASTx) Exec(
	ctx context.Context,
	query string,
	args ...any,
) (coredb.Result, error) {
	if strings.Contains(query, "UPDATE workspace_team_members SET role_label=") {
		tx.seen++
		if tx.seen == tx.failAt {
			return proposalRowsAffected(0), nil
		}
	}
	return tx.Tx.Exec(ctx, query, args...)
}

type proposalRowsAffected int64

func (result proposalRowsAffected) RowsAffected() int64 { return int64(result) }
