package taskboard

import (
	"context"
	"fmt"
	"strings"
	"time"

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

func (txs TxStore) AddTaskCheckpoint(ctx context.Context, request AddTaskCheckpointRequest) (AddTaskCheckpointResponse, error) {
	workspaceID, err := request.Context.requireWorkspaceID()
	if err != nil {
		return AddTaskCheckpointResponse{}, err
	}
	if err := validateTaskCheckpointInput(request.Checkpoint); err != nil {
		return AddTaskCheckpointResponse{}, err
	}

	switch request.Context.principalType() {
	case PrincipalTypeUser:
		userID, _, err := request.Context.requireUser()
		if err != nil {
			return AddTaskCheckpointResponse{}, err
		}
		taskID, err := request.Task.requireTaskID()
		if err != nil {
			return AddTaskCheckpointResponse{}, err
		}
		task, err := txs.addTaskCheckpointForUser(ctx, userID, workspaceID, taskID, request.Checkpoint)
		if err != nil {
			return AddTaskCheckpointResponse{}, err
		}
		return AddTaskCheckpointResponse{Task: task}, nil
	case PrincipalTypeAgent:
		agent, err := request.Context.requireAgentActor()
		if err != nil {
			return AddTaskCheckpointResponse{}, err
		}
		taskNumber, err := request.Task.requireTaskNumber()
		if err != nil {
			return AddTaskCheckpointResponse{}, err
		}
		task, err := txs.addTaskCheckpointForAgent(ctx, agent, request.Board, taskNumber, request.Checkpoint)
		if err != nil {
			return AddTaskCheckpointResponse{}, err
		}
		return AddTaskCheckpointResponse{Task: task}, nil
	default:
		return AddTaskCheckpointResponse{}, badRequest("context subject principalType is required")
	}
}

func (txs TxStore) addTaskCheckpointForUser(ctx context.Context, userID string, workspaceID string, taskID string, checkpoint TaskCheckpointInput) (Task, error) {
	tx := txs.tx
	channelID, _, _, err := txs.lockTaskRow(ctx, tx, workspaceID, taskID)
	if err != nil {
		return Task{}, err
	}
	channelAccess, err := txs.loadAuthorizedUserTaskChannelAccess(ctx, tx, userID, workspaceID, channelID, taskWriteChannelAction)
	if err != nil {
		return Task{}, err
	}
	// TOCTOU fence: the Task row is already locked (lockTaskRow FOR UPDATE);
	// lock the user's active membership on the canonical board Channel before
	// writing, so a concurrent board-membership revoke serializes deterministically.
	if err := txs.lockActiveHumanBoardChannelAuthority(ctx, tx, userID, workspaceID, channelID); err != nil {
		return Task{}, err
	}
	return txs.addTaskCheckpointInTx(ctx, tx, workspaceID, taskID, channelAccess.WorkspaceMember.Member.ID, checkpoint)
}

func (txs TxStore) addTaskCheckpointForAgent(ctx context.Context, agent AgentActor, board BoardRef, taskNumber int64, checkpoint TaskCheckpointInput) (Task, error) {
	tx := txs.tx
	_, taskID, err := txs.loadAccessibleAgentTaskID(ctx, tx, agent, board, taskNumber)
	if err != nil {
		return Task{}, err
	}
	// TOCTOU fence: lock the canonical Task row FOR UPDATE (deriving the board
	// Channel server-side) and the agent's active membership on it in a fixed
	// order, so a concurrent board-membership revoke / Task delete / board move
	// serializes deterministically.
	if _, err := txs.lockTaskBoardChannelAuthority(ctx, tx, agent.WorkspaceID, taskID, agent.MemberID); err != nil {
		return Task{}, err
	}
	return txs.addTaskCheckpointInTx(ctx, tx, agent.WorkspaceID, taskID, agent.MemberID, checkpoint)
}

func (txs TxStore) addTaskCheckpointInTx(ctx context.Context, tx coredb.Tx, workspaceID string, taskID string, actorMemberID string, checkpoint TaskCheckpointInput) (Task, error) {
	if err := validateTaskCheckpointInput(checkpoint); err != nil {
		return Task{}, err
	}
	text := strings.TrimSpace(checkpoint.Text)
	now := time.Now().UTC()
	var nextPosition int64
	if err := tx.QueryRow(ctx, `
SELECT COALESCE(MAX(position), -1) + 1
FROM task_checklist_items
WHERE task_id = $1
  AND list_kind = $2
`, taskID, taskChecklistKindCheckpoint).Scan(&nextPosition); err != nil {
		return Task{}, fmt.Errorf("load next checkpoint position: %w", err)
	}
	checkpointID, err := newTaskChecklistItemID()
	if err != nil {
		return Task{}, fmt.Errorf("generate task checkpoint id: %w", err)
	}
	if _, err := tx.Exec(ctx, `
INSERT INTO task_checklist_items (
    id,
    task_id,
    list_kind,
    position,
    item_text,
    is_checked,
    created_at,
    updated_at
) VALUES ($1, $2, $3, $4, $5, FALSE, $6, $6)
`, checkpointID, taskID, taskChecklistKindCheckpoint, nextPosition, text, now); err != nil {
		return Task{}, fmt.Errorf("insert task checkpoint: %w", err)
	}
	if _, err := tx.Exec(ctx, `
UPDATE tasks
SET last_activity_at = $2,
    updated_at = $2
WHERE id = $1
`, taskID, now); err != nil {
		return Task{}, fmt.Errorf("touch task after checkpoint: %w", err)
	}
	if err := applyTaskTypedEventPlan(ctx, tx, taskTypedEventIntent(taskID, actorMemberID, taskEventCheckpointAdded, text, now)); err != nil {
		return Task{}, err
	}
	return txs.taskByID(ctx, tx, workspaceID, taskID)
}

func loadTaskArtifacts(ctx context.Context, queryer coredb.Queryer, taskID string) ([]TaskArtifact, error) {
	return loadTaskArtifactsWithOptions(ctx, queryer, taskID, 0, false)
}

func loadTaskArtifactMetadataLimit(ctx context.Context, queryer coredb.Queryer, taskID string, limit int) ([]TaskArtifact, error) {
	return loadTaskArtifactsWithOptions(ctx, queryer, taskID, limit, true)
}

func loadTaskArtifactsWithOptions(ctx context.Context, queryer coredb.Queryer, taskID string, limit int, metadataOnly bool) ([]TaskArtifact, error) {
	payloadColumn := "payload_json"
	if metadataOnly {
		payloadColumn = "NULL"
	}
	limitClause := ""
	args := []any{taskID}
	if limit > 0 {
		limitClause = "LIMIT $2"
		args = append(args, limit+1)
	}
	rows, err := queryer.Query(ctx, `
SELECT id, kind, title, `+payloadColumn+`, created_by_ref, created_at
FROM task_artifacts
WHERE task_id = $1
ORDER BY created_at ASC, id ASC
`+limitClause, args...)
	if err != nil {
		return nil, fmt.Errorf("query task artifacts: %w", err)
	}
	defer rows.Close()

	artifacts := make([]TaskArtifact, 0)
	for rows.Next() {
		var artifact TaskArtifact
		var payload []byte
		if err := rows.Scan(&artifact.ID, &artifact.Kind, &artifact.Title, &payload, &artifact.CreatedByRef, &artifact.CreatedAt); err != nil {
			return nil, fmt.Errorf("scan task artifact: %w", err)
		}
		if len(payload) > 0 {
			artifact.Payload = append([]byte(nil), payload...)
		}
		artifacts = append(artifacts, artifact)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate task artifacts: %w", err)
	}
	if limit > 0 && len(artifacts) > limit {
		artifacts = artifacts[:limit]
	}
	if err := hydrateTaskArtifactPrincipalProfiles(ctx, queryer, artifacts); err != nil {
		return nil, err
	}
	return artifacts, nil
}

func hydrateTaskArtifactPrincipalProfiles(ctx context.Context, queryer coredb.Queryer, artifacts []TaskArtifact) error {
	refs := make([]string, 0, len(artifacts))
	for _, artifact := range artifacts {
		refs = append(refs, artifact.CreatedByRef)
	}
	profiles, err := loadTaskPrincipalProfilesByMemberRef(ctx, queryer, refs...)
	if err != nil {
		return err
	}
	for index := range artifacts {
		if profile, ok := profiles[strings.TrimSpace(artifacts[index].CreatedByRef)]; ok {
			artifacts[index].CreatedByProfile = &profile
		}
	}
	return nil
}
