package taskboard

import (
	"context"
	"fmt"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
)

// grantTaskCollaboratorInTx is the transaction-local canonical grant primitive
// shared by direct owner-add (TxStore.AddTaskCollaborator) and the approved
// collaborator-request path (the Approval effect). It applies the SAME role /
// expiry row, scope execution, unwatched state, and grant-added notification
// semantics, so an approved request is byte-for-byte equivalent to a direct
// owner-add.
//
// The caller must already have resolved the target, locked the canonical
// Task->board Channel authority, and authorized the actor as a collaborator
// manager (execution owner or workspace admin). This function owns the grant
// upsert only — it never re-resolves authority and never calls HTTP.
//
// Returns the resulting collaborator row and whether the relation was newly
// created (a new row was inserted, so the grant-added notification must be
// queued exactly once).
func (txs TxStore) grantTaskCollaboratorInTx(
	ctx context.Context,
	tx coredb.Tx,
	workspaceID string,
	target taskCollaboratorTarget,
	memberID string,
	role string,
	expiresAt *time.Time,
	addedByMemberID string,
	now time.Time,
) (TaskCollaborator, bool, error) {
	execution, err := txs.ensureTaskScopeExecution(ctx, tx, target, now)
	if err != nil {
		return TaskCollaborator{}, false, err
	}
	if err := cleanupExpiredTaskScopeParticipants(ctx, tx, []taskScopeExecution{execution}, now); err != nil {
		return TaskCollaborator{}, false, err
	}

	existingID, err := lockActiveTaskCollaboratorID(ctx, tx, execution.ID, memberID, now)
	if err != nil {
		return TaskCollaborator{}, false, err
	}
	if existingID != "" {
		if _, err := tx.Exec(ctx, `
UPDATE task_scope_participant_metadata
SET role = $2,
    expires_at = $3,
    updated_at = $4
WHERE id = $1
`, existingID, role, expiresAt, now); err != nil {
			return TaskCollaborator{}, false, fmt.Errorf("update task collaborator: %w", err)
		}
		if err := ensureTaskCollaboratorScopeDefaultUnwatched(ctx, tx, target, []string{addedByMemberID, memberID}, now); err != nil {
			return TaskCollaborator{}, false, err
		}
		collaborator, err := loadTaskCollaboratorByID(ctx, tx, existingID, now)
		if err != nil {
			return TaskCollaborator{}, false, err
		}
		return collaborator, false, nil
	}

	collaboratorID, err := newTaskCollaboratorID()
	if err != nil {
		return TaskCollaborator{}, false, fmt.Errorf("generate task collaborator id: %w", err)
	}
	if _, err := tx.Exec(ctx, `
INSERT INTO task_scope_participant_metadata (
    id,
    owner_scope_execution_id,
    member_id,
    active_key,
    role,
    added_by_member_id,
    added_at,
    add_notification_state,
    add_notification_queued_at,
    notified_at,
    expires_at,
    created_at,
    updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, 'none', NULL, NULL, $8, $7, $7)
`, collaboratorID, execution.ID, memberID, taskScopeParticipantActiveKey(execution.ID, memberID), role, addedByMemberID, now, expiresAt); err != nil {
		return TaskCollaborator{}, false, fmt.Errorf("insert task collaborator: %w", err)
	}
	if err := ensureTaskCollaboratorScopeDefaultUnwatched(ctx, tx, target, []string{addedByMemberID, memberID}, now); err != nil {
		return TaskCollaborator{}, false, err
	}
	if err := queueCollaboratorGrantAddedNotification(ctx, tx, collaboratorID, now); err != nil {
		return TaskCollaborator{}, false, err
	}
	collaborator, err := loadTaskCollaboratorByID(ctx, tx, collaboratorID, now)
	if err != nil {
		return TaskCollaborator{}, false, err
	}
	return collaborator, true, nil
}
