package taskboard

import (
	"context"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/access"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
)

const taskDetailSectionDefaultLimit = 10

type taskDetailReadOptions struct {
	skipDiscussionReactions bool
	skipTimeline            bool
	taskOnly                bool
	viewerMemberID          string
	viewerType              string
}

func (txs TxStore) GetTaskDetail(ctx context.Context, request GetTaskDetailRequest) (GetTaskDetailResponse, error) {
	workspaceID, err := request.Context.requireWorkspaceID()
	if err != nil {
		return GetTaskDetailResponse{}, err
	}
	options := taskDetailReadOptions{
		skipDiscussionReactions: request.SkipDiscussionReactions,
		skipTimeline:            request.SkipTimeline,
		taskOnly:                request.TaskOnly,
	}

	switch request.Context.principalType() {
	case PrincipalTypeUser:
		userID, _, err := request.Context.requireUser()
		if err != nil {
			return GetTaskDetailResponse{}, err
		}
		detail, err := txs.getTaskDetailForUser(ctx, userID, workspaceID, request.Board, request.Task, options)
		if err != nil {
			return GetTaskDetailResponse{}, err
		}
		return GetTaskDetailResponse{Detail: detail}, nil
	case PrincipalTypeAgent:
		agent, err := request.Context.requireAgentActor()
		if err != nil {
			return GetTaskDetailResponse{}, err
		}
		detail, err := txs.getTaskDetailForAgent(ctx, agent, request.Board, request.Task, options)
		if err != nil {
			return GetTaskDetailResponse{}, err
		}
		return GetTaskDetailResponse{Detail: detail}, nil
	default:
		return GetTaskDetailResponse{}, badRequest("context subject principalType is required")
	}
}

func (txs TxStore) getTaskDetailForUser(ctx context.Context, userID string, workspaceID string, board BoardRef, taskRef TaskRef, options taskDetailReadOptions) (TaskDetail, error) {
	task, err := txs.resolveReadableUserTask(ctx, txs.tx, userID, workspaceID, board, taskRef)
	if err != nil {
		return TaskDetail{}, err
	}
	membership, err := access.LoadHumanWorkspaceMember(ctx, txs.tx, userID, workspaceID)
	if err != nil {
		return TaskDetail{}, err
	}
	options.viewerMemberID = membership.Member.ID
	options.viewerType = message.ScopeActorHuman
	detail, err := txs.loadTaskDetail(ctx, txs.tx, workspaceID, task.ID, PrincipalTypeUser, options)
	if err != nil {
		return TaskDetail{}, err
	}
	if err := attachResolvedMentionsForTaskTimelineEntries(ctx, txs.tx, workspaceID, membership.Member.ID, detail.Task.TaskScopeID, detail.Timeline); err != nil {
		return TaskDetail{}, err
	}
	if err := attachTaskReadStateForUser(ctx, txs.tx, workspaceID, userID, &detail.Task); err != nil {
		return TaskDetail{}, err
	}
	return detail, nil
}

func (txs TxStore) getTaskDetailForAgent(ctx context.Context, agent AgentActor, board BoardRef, taskRef TaskRef, options taskDetailReadOptions) (TaskDetail, error) {
	task, err := txs.resolveReadableAgentTask(ctx, txs.tx, agent, board, taskRef)
	if err != nil {
		return TaskDetail{}, err
	}

	options.viewerMemberID = agent.MemberID
	options.viewerType = message.ScopeActorAgent
	detail, err := txs.loadTaskDetail(ctx, txs.tx, agent.WorkspaceID, task.ID, PrincipalTypeAgent, options)
	if err != nil {
		return TaskDetail{}, err
	}
	if err := attachResolvedMentionsForTaskTimelineEntries(ctx, txs.tx, agent.WorkspaceID, agent.MemberID, detail.Task.TaskScopeID, detail.Timeline); err != nil {
		return TaskDetail{}, err
	}
	return detail, nil
}

func attachResolvedMentionsForTaskTimelineEntries(ctx context.Context, queryer coredb.Queryer, workspaceID string, viewerMemberID string, taskScopeID string, entries []TaskTimelineEntry) error {
	if len(entries) == 0 {
		return nil
	}
	discussions := make([]TaskDiscussionEntry, 0, len(entries))
	indexes := make([]int, 0, len(entries))
	for index := range entries {
		if entries[index].Kind != TaskTimelineEntryKindDiscussion || entries[index].Discussion == nil {
			continue
		}
		discussions = append(discussions, *entries[index].Discussion)
		indexes = append(indexes, index)
	}
	if len(discussions) == 0 {
		return nil
	}
	if err := attachResolvedMentionsForTaskDiscussionEntries(ctx, queryer, workspaceID, viewerMemberID, taskScopeID, discussions); err != nil {
		return err
	}
	for index, entryIndex := range indexes {
		discussion := discussions[index]
		entries[entryIndex].Discussion = &discussion
	}
	return nil
}

func (txs TxStore) resolveReadableUserTask(
	ctx context.Context,
	tx coredb.QueryExecer,
	userID string,
	workspaceID string,
	board BoardRef,
	taskRef TaskRef,
) (Task, error) {
	if taskID := strings.TrimSpace(taskRef.TaskID); taskID != "" {
		// TOCTOU fence: lock the canonical Task->board Channel row and revalidate
		// the full human Channel authority so a concurrent board-membership revoke /
		// archive / effective-close / Channel delete / member deactivate / Task
		// delete / board move serializes deterministically even for a read.
		channelID, err := txs.lockHumanTaskBoardChannelAuthority(ctx, tx, userID, workspaceID, taskID)
		if err != nil {
			return Task{}, err
		}
		if _, err := txs.loadAuthorizedUserTaskChannelAccess(ctx, tx, userID, workspaceID, channelID, taskReadChannelAction); err != nil {
			return Task{}, err
		}
		return txs.taskByID(ctx, tx, workspaceID, taskID)
	}

	channelID, err := board.requireChannelID()
	if err != nil {
		return Task{}, err
	}
	if _, err := txs.loadAuthorizedUserTaskChannelAccess(ctx, tx, userID, workspaceID, channelID, taskReadChannelAction); err != nil {
		return Task{}, err
	}
	taskNumber, err := taskRef.requireTaskNumber()
	if err != nil {
		return Task{}, err
	}
	return txs.taskByNumber(ctx, tx, workspaceID, channelID, taskNumber)
}

func (txs TxStore) resolveReadableAgentTask(
	ctx context.Context,
	tx coredb.QueryExecer,
	agent AgentActor,
	board BoardRef,
	taskRef TaskRef,
) (Task, error) {
	if taskID := strings.TrimSpace(taskRef.TaskID); taskID != "" {
		// TOCTOU fence: lock the canonical Task->board Channel row and the
		// agent's active membership on it so a concurrent board-membership
		// revoke / Task delete / board move serializes deterministically even for
		// a read, and the Channel is derived server-side from the locked row.
		channelID, err := txs.lockTaskBoardChannelAuthority(ctx, tx, agent.WorkspaceID, taskID, agent.MemberID)
		if err != nil {
			return Task{}, err
		}
		if err := txs.authorizeAccessibleAgentChannelID(ctx, tx, agent, channelID); err != nil {
			return Task{}, err
		}
		return txs.taskByID(ctx, tx, agent.WorkspaceID, taskID)
	}

	channelID, err := txs.resolveAccessibleAgentBoard(ctx, tx, agent, board)
	if err != nil {
		return Task{}, err
	}
	taskNumber, err := taskRef.requireTaskNumber()
	if err != nil {
		return Task{}, err
	}
	return txs.taskByNumber(ctx, tx, agent.WorkspaceID, channelID, taskNumber)
}

func (txs TxStore) loadTaskDetail(ctx context.Context, queryer coredb.QueryExecer, workspaceID string, taskID string, actorPrincipalType PrincipalType, options taskDetailReadOptions) (TaskDetail, error) {
	task, err := txs.taskByID(ctx, queryer, workspaceID, taskID)
	if err != nil {
		return TaskDetail{}, err
	}
	if options.taskOnly {
		return TaskDetail{Task: task}, nil
	}
	parentTask, err := txs.resolveParentTaskSummary(
		ctx,
		queryer,
		workspaceID,
		task.ChannelID,
		task.ParentTaskRef,
	)
	if err != nil {
		return TaskDetail{}, err
	}
	subtaskPage, err := txs.listTasksByParentTaskPage(ctx, queryer, workspaceID, taskID, TaskSubtasksDefaultPageSize, SubtaskPageCursor{})
	if err != nil {
		return TaskDetail{}, err
	}
	checkpoints, err := loadTaskChecklistLimit(ctx, queryer, taskID, taskChecklistKindCheckpoint, taskDetailSectionDefaultLimit)
	if err != nil {
		return TaskDetail{}, err
	}
	timelinePage := GetTaskTimelineResponse{}
	if !options.skipTimeline {
		timelineReader := NewTaskTimelineReadService(queryer)
		timelinePage, err = timelineReader.loadTimelinePage(ctx, workspaceID, taskID, TaskTimelineCursor{}, taskDetailTimelinePageSize, taskTimelineReadOptions{
			skipDiscussionReactions: options.skipDiscussionReactions,
			viewerMemberID:          options.viewerMemberID,
			viewerType:              options.viewerType,
		})
		if err != nil {
			return TaskDetail{}, err
		}
	}
	acceptanceCriteria, err := loadTaskChecklistLimit(ctx, queryer, taskID, taskChecklistKindAcceptanceCriteria, taskDetailSectionDefaultLimit)
	if err != nil {
		return TaskDetail{}, err
	}
	reviewChecklist, err := loadTaskChecklistLimit(ctx, queryer, taskID, taskChecklistKindReviewChecklist, taskDetailSectionDefaultLimit)
	if err != nil {
		return TaskDetail{}, err
	}
	pinnedMessageRefs, pinnedMessageRefsHasMore, relevantURLs, relevantURLsHasMore, workspaceRefs, workspaceRefsHasMore, err := loadTaskContextRefsLimit(ctx, queryer, taskID, taskDetailContextRefLimit)
	if err != nil {
		return TaskDetail{}, err
	}
	reviewHistory, err := loadTaskReviewHistoryLimit(ctx, queryer, taskID, taskDetailSectionDefaultLimit)
	if err != nil {
		return TaskDetail{}, err
	}
	artifacts, err := loadTaskArtifactMetadataLimit(ctx, queryer, taskID, taskDetailSectionDefaultLimit)
	if err != nil {
		return TaskDetail{}, err
	}
	return TaskDetail{
		Task:                     task,
		ParentTask:               parentTask,
		Checkpoints:              checkpoints,
		Timeline:                 timelinePage.Entries,
		TimelineHasMore:          timelinePage.HasMore,
		TimelineNextCursor:       timelinePage.NextCursor,
		Subtasks:                 subtaskPage.Tasks,
		SubtasksHasMore:          subtaskPage.HasMore,
		SubtasksNextCursor:       subtaskPage.NextCursor,
		AcceptanceCriteria:       acceptanceCriteria,
		ReviewChecklist:          reviewChecklist,
		PinnedMessageRefs:        pinnedMessageRefs,
		PinnedMessageRefsHasMore: pinnedMessageRefsHasMore,
		RelevantURLs:             relevantURLs,
		RelevantURLsHasMore:      relevantURLsHasMore,
		WorkspaceRefs:            workspaceRefs,
		WorkspaceRefsHasMore:     workspaceRefsHasMore,
		ReviewHistory:            reviewHistory,
		Artifacts:                artifacts,
	}, nil
}
