package taskboard

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

	"github.com/tidbcloud/AgentTeam/internal/core/domain"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/tasktimeline"
)

type preparedCreateSubtasks struct {
	workspaceID     string
	metadata        CreateTaskMetadata
	parentTaskScope *domain.MessageScopeValue
	inputs          []TaskCreateInput
}

func (txs TxStore) CreateSubtasks(ctx context.Context, request CreateSubtasksRequest) (CreateSubtasksResponse, error) {
	prepared, err := prepareCreateSubtasks(request)
	if err != nil {
		return CreateSubtasksResponse{}, err
	}
	switch request.Context.principalType() {
	case PrincipalTypeUser:
		return txs.createSubtasksRequestForUser(ctx, request, prepared)
	case PrincipalTypeAgent:
		return txs.createSubtasksRequestForAgent(ctx, request, prepared)
	default:
		return CreateSubtasksResponse{}, badRequest("context subject principalType is required")
	}
}

func prepareCreateSubtasks(request CreateSubtasksRequest) (preparedCreateSubtasks, error) {
	workspaceID, err := request.Context.requireWorkspaceID()
	if err != nil {
		return preparedCreateSubtasks{}, err
	}
	metadata, err := normalizeCreateTaskMetadata(request.Metadata)
	if err != nil {
		return preparedCreateSubtasks{}, err
	}
	parentTaskScope, err := normalizeCreateSubtasksParentScope(request.ParentTaskScope)
	if err != nil {
		return preparedCreateSubtasks{}, err
	}
	inputs, err := createTaskInputs(request.Input, request.Titles)
	if err != nil {
		return preparedCreateSubtasks{}, err
	}
	if err := validateTaskCreateInputs(inputs); err != nil {
		return preparedCreateSubtasks{}, err
	}
	return preparedCreateSubtasks{
		workspaceID:     workspaceID,
		metadata:        metadata,
		parentTaskScope: parentTaskScope,
		inputs:          inputs,
	}, nil
}

func (txs TxStore) createSubtasksRequestForUser(
	ctx context.Context,
	request CreateSubtasksRequest,
	prepared preparedCreateSubtasks,
) (CreateSubtasksResponse, error) {
	if prepared.parentTaskScope != nil {
		return CreateSubtasksResponse{}, badRequest("parentTaskScope is only supported for Agent task creation")
	}
	userID, displayName, err := request.Context.requireUser()
	if err != nil {
		return CreateSubtasksResponse{}, err
	}
	parentTaskID, err := request.Task.requireTaskID()
	if err != nil {
		return CreateSubtasksResponse{}, err
	}
	channelID, tasks, createdTasks, err := txs.createSubtasksForUser(
		ctx,
		userID,
		displayName,
		prepared.workspaceID,
		parentTaskID,
		prepared.inputs,
		prepared.metadata,
	)
	if err != nil {
		return CreateSubtasksResponse{}, err
	}
	return CreateSubtasksResponse{
		ChannelID:    channelID,
		Tasks:        tasks,
		CreatedTasks: createdTasks,
	}, nil
}

func (txs TxStore) createSubtasksRequestForAgent(
	ctx context.Context,
	request CreateSubtasksRequest,
	prepared preparedCreateSubtasks,
) (CreateSubtasksResponse, error) {
	agent, err := request.Context.requireAgentActor()
	if err != nil {
		return CreateSubtasksResponse{}, err
	}
	if err := validateCreateSubtasksLocator(request, prepared.parentTaskScope); err != nil {
		return CreateSubtasksResponse{}, err
	}
	response, err := txs.createAgentSubtasks(ctx, request, agent, prepared)
	if err != nil {
		return CreateSubtasksResponse{}, err
	}
	return response, nil
}

func validateCreateSubtasksLocator(
	request CreateSubtasksRequest,
	parentTaskScope *domain.MessageScopeValue,
) error {
	if parentTaskScope == nil {
		return nil
	}
	if strings.TrimSpace(request.Board.ChannelID) != "" ||
		strings.TrimSpace(request.Board.Channel) != "" ||
		strings.TrimSpace(request.Task.TaskID) != "" ||
		request.Task.TaskNumber != 0 {
		return badRequest("parentTaskScope cannot be combined with channel or parent")
	}
	return nil
}

func (txs TxStore) createAgentSubtasks(
	ctx context.Context,
	request CreateSubtasksRequest,
	agent AgentActor,
	prepared preparedCreateSubtasks,
) (CreateSubtasksResponse, error) {
	if prepared.parentTaskScope != nil {
		channelID, tasks, createdTasks, err := txs.createSubtasksForAgentTaskScope(
			ctx,
			agent,
			*prepared.parentTaskScope,
			request.SourceChannelID,
			prepared.inputs,
			prepared.metadata,
		)
		if err != nil {
			return CreateSubtasksResponse{}, fmt.Errorf("create subtasks from parentTaskScope: %w", err)
		}
		return newCreateSubtasksResponse(channelID, tasks, createdTasks), nil
	}
	parentTaskNumber, err := request.Task.requireTaskNumber()
	if err != nil {
		return CreateSubtasksResponse{}, err
	}
	channelID, tasks, createdTasks, err := txs.createSubtasksForAgent(
		ctx,
		agent,
		request.Board,
		parentTaskNumber,
		request.SourceChannelID,
		prepared.inputs,
		prepared.metadata,
	)
	return newCreateSubtasksResponse(channelID, tasks, createdTasks), err
}

func newCreateSubtasksResponse(
	channelID string,
	tasks []Task,
	createdTasks []CreatedTask,
) CreateSubtasksResponse {
	return CreateSubtasksResponse{
		ChannelID:    channelID,
		Tasks:        tasks,
		CreatedTasks: createdTasks,
	}
}

func normalizeCreateSubtasksParentScope(value *domain.MessageScopeValue) (*domain.MessageScopeValue, error) {
	if value == nil {
		return nil, nil
	}
	scope, err := domain.NormalizeMessageScopeValue(*value)
	if err != nil {
		return nil, badRequest(fmt.Sprintf("parentTaskScope is invalid: %v", err))
	}
	if scope.Kind != domain.MessageScopeKindTask {
		return nil, badRequest("parentTaskScope kind must be task")
	}
	return &scope, nil
}

func (txs TxStore) createSubtasksForUser(
	ctx context.Context,
	userID string,
	displayName string,
	workspaceID string,
	parentTaskID string,
	inputs []TaskCreateInput,
	metadata CreateTaskMetadata,
) (string, []Task, []CreatedTask, error) {
	channelID, _, _, err := txs.lockTaskRow(ctx, txs.tx, workspaceID, parentTaskID)
	if err != nil {
		return "", nil, nil, err
	}
	channelAccess, err := txs.loadAuthorizedUserTaskChannelAccess(ctx, txs.tx, userID, workspaceID, channelID, taskWriteChannelAction)
	if err != nil {
		return "", nil, nil, err
	}
	tasks, createdTasks, err := txs.createSubtasksInTx(
		ctx,
		txs.tx,
		workspaceID,
		channelID,
		parentTaskID,
		channelAccess.WorkspaceMember.Member.ID,
		inputs,
		metadata,
		false,
		humanTaskFencerFor(userID, workspaceID, taskWriteChannelAction),
	)
	if err != nil {
		return "", nil, nil, err
	}
	for index := range tasks {
		if tasks[index].CreatedByProfile == nil {
			tasks[index].CreatedByProfile = taskHumanPrincipalProfile(userID, displayName)
			tasks[index].CreatedByType, tasks[index].CreatedByID, tasks[index].CreatedByName = taskLegacyCreatedByFromPrincipalProfile(tasks[index].CreatedByProfile, tasks[index].CreatedByType, tasks[index].CreatedByID, tasks[index].CreatedByName)
		}
	}
	return channelID, tasks, createdTasks, nil
}

func (txs TxStore) createSubtasksForAgent(
	ctx context.Context,
	agent AgentActor,
	board BoardRef,
	parentTaskNumber int64,
	sourceChannelID string,
	inputs []TaskCreateInput,
	metadata CreateTaskMetadata,
) (string, []Task, []CreatedTask, error) {
	channelID, err := txs.resolveAccessibleAgentBoard(ctx, txs.tx, agent, board)
	if err != nil {
		return "", nil, nil, err
	}
	if err := validateCreateSubtasksSourceChannel(channelID, sourceChannelID, metadata); err != nil {
		return "", nil, nil, err
	}
	parentTask, err := txs.taskByNumber(ctx, txs.tx, agent.WorkspaceID, channelID, parentTaskNumber)
	if err != nil {
		return "", nil, nil, err
	}
	tasks, createdTasks, err := txs.createSubtasksInTx(
		ctx,
		txs.tx,
		agent.WorkspaceID,
		channelID,
		parentTask.ID,
		agent.MemberID,
		inputs,
		metadata,
		true,
		agentTaskFencerFor(agent),
	)
	if err != nil {
		return "", nil, nil, err
	}
	return channelID, tasks, createdTasks, nil
}

func (txs TxStore) createSubtasksForAgentTaskScope(
	ctx context.Context,
	agent AgentActor,
	parentTaskScope domain.MessageScopeValue,
	sourceChannelID string,
	inputs []TaskCreateInput,
	metadata CreateTaskMetadata,
) (string, []Task, []CreatedTask, error) {
	taskID, err := txs.loadTaskIDForTaskScope(
		ctx,
		txs.tx,
		agent.WorkspaceID,
		parentTaskScope.ScopeID,
	)
	if err != nil {
		return "", nil, nil, fmt.Errorf("load parent Task for parentTaskScope: %w", err)
	}
	channelID, _, _, err := txs.lockTaskRow(ctx, txs.tx, agent.WorkspaceID, taskID)
	if err != nil {
		return "", nil, nil, fmt.Errorf("lock parent Task for parentTaskScope: %w", err)
	}
	canonicalScopeID, err := txs.loadTaskScopeID(ctx, txs.tx, taskID)
	if err != nil {
		return "", nil, nil, fmt.Errorf("load canonical parent Task scope: %w", err)
	}
	if strings.TrimSpace(canonicalScopeID) != parentTaskScope.ScopeID {
		return "", nil, nil, ErrForbidden
	}
	// Authorize through the single canonical Agent Task-channel authorizer: the
	// server resolved channelID from the active Task/board authority, and the
	// same-workspace active Agent+Machine+Member must currently hold active
	// membership on that channel scope. The client-supplied parent taskScope is
	// only a location/consistency hint, never the authorization fact.
	if err := txs.ensureAgentCanAccessTaskChannel(ctx, txs.tx, agent, channelID); err != nil {
		return "", nil, nil, err
	}
	if err := validateCreateSubtasksSourceChannel(channelID, sourceChannelID, metadata); err != nil {
		return "", nil, nil, err
	}
	tasks, createdTasks, err := txs.createSubtasksInTx(
		ctx,
		txs.tx,
		agent.WorkspaceID,
		channelID,
		taskID,
		agent.MemberID,
		inputs,
		metadata,
		true,
		agentTaskFencerFor(agent),
	)
	if err != nil {
		return "", nil, nil, err
	}
	return channelID, tasks, createdTasks, nil
}

func validateCreateSubtasksSourceChannel(
	parentChannelID string,
	sourceChannelID string,
	metadata CreateTaskMetadata,
) error {
	parentChannelID = strings.TrimSpace(parentChannelID)
	sourceChannelID = strings.TrimSpace(sourceChannelID)
	metadataChannelID := strings.TrimSpace(metadata.SourceThreadParentChannelID)
	if sourceChannelID != "" && sourceChannelID != parentChannelID {
		return badRequest("source target conflicts with parent Task channel")
	}
	if metadataChannelID != "" && metadataChannelID != parentChannelID {
		return badRequest("source thread conflicts with parent Task channel")
	}
	return nil
}

func (txs TxStore) createSubtasksInTx(
	ctx context.Context,
	tx coredb.Tx,
	workspaceID string,
	channelID string,
	parentTaskID string,
	actorMemberID string,
	inputs []TaskCreateInput,
	metadata CreateTaskMetadata,
	strictFieldOptions bool,
	fencer taskAuthorityFencer,
) ([]Task, []CreatedTask, error) {
	if len(inputs) == 0 {
		return nil, nil, badRequest("at least one task is required")
	}
	// Parent task row is locked first; derive the canonical board Channel
	// server-side from the locked row and lock the actor's active authority chain
	// on it inside this transaction, before any child Task/event/scope/participant
	// row is inserted. The caller-passed channelID is only a consistency hint,
	// never the authorization fact.
	lockedChannelID, _, _, err := txs.lockTaskRow(ctx, tx, workspaceID, parentTaskID)
	if err != nil {
		return nil, nil, fmt.Errorf("lock nested parent Task: %w", err)
	}
	if err := fencer.lockChannelAuthority(ctx, tx, lockedChannelID); err != nil {
		return nil, nil, fmt.Errorf("lock nested parent Channel authority: %w", err)
	}
	parentTask, err := txs.taskByID(ctx, tx, workspaceID, parentTaskID)
	if err != nil {
		return nil, nil, fmt.Errorf("load nested parent Task: %w", err)
	}
	if parentTask.Status == string(StatusInReview) || parentTask.Status == string(StatusDone) {
		return nil, nil, conflict("cannot create subtasks for a task in review or done")
	}
	parentAnchor, err := txs.loadTaskCollaboratorTaskAnchor(ctx, tx, workspaceID, parentTaskID)
	if err != nil {
		return nil, nil, fmt.Errorf("load nested parent collaborator anchor: %w", err)
	}

	// Parent-task locators bypass the board-number resolver, so ensure the
	// canonical board counter exists before allocating the nested child's
	// number. This is the same production initialization used by root creates.
	board, err := txs.ensureTaskBoard(ctx, tx, workspaceID, channelID)
	if err != nil {
		return nil, nil, fmt.Errorf("ensure nested parent board: %w", err)
	}
	resolvedInputs := make([]taskInsertInput, 0, len(inputs))
	for _, input := range inputs {
		resolved, err := txs.resolveTaskInsertInput(ctx, tx, workspaceID, channelID, actorMemberID, input, metadata, strictFieldOptions)
		if err != nil {
			return nil, nil, fmt.Errorf("resolve nested Task input: %w", err)
		}
		resolved.ParentTaskRef = parentTask.ID
		// Subtasks remain part of the parent's execution, even when an Agent
		// creates them dynamically. Snapshotting the parent's effective owner
		// keeps the frozen Human Workflow owner authoritative while preserving
		// the Agent as the immutable Task creator.
		resolved.ExecutionOwnerMemberID = parentAnchor.ExecutionOwnerMemberID
		resolvedInputs = append(resolvedInputs, resolved)
	}

	nextTaskNumber, err := txs.allocateTaskNumbers(ctx, tx, board, len(resolvedInputs))
	if err != nil {
		return nil, nil, err
	}

	now := time.Now().UTC()
	taskIDs := make([]string, 0, len(resolvedInputs))
	createdTasks := make([]CreatedTask, 0, len(resolvedInputs))
	for index, input := range resolvedInputs {
		taskNumber := nextTaskNumber + int64(index)
		taskID, taskScopeID, err := txs.insertTask(ctx, tx, board, taskNumber, actorMemberID, input, now)
		if err != nil {
			return nil, nil, fmt.Errorf("insert nested Task: %w", err)
		}
		if err := ensureTaskScopeParticipantsDefaultUnwatched(ctx, tx, taskScopeID, []string{input.AssigneeMemberID}, now); err != nil {
			return nil, nil, err
		}
		activityIntents := []tasktimeline.TypedActivityIntent{
			taskTypedEventIntent(taskID, actorMemberID, taskEventCreated, "", now),
		}
		if input.AssigneeMemberID != "" {
			activityIntents = append(activityIntents, taskTypedEventIntent(taskID, actorMemberID, taskEventAssigned, input.AssigneeMemberID, now))
		}
		if err := applyTaskTypedEventPlan(ctx, tx, activityIntents...); err != nil {
			return nil, nil, err
		}
		taskIDs = append(taskIDs, taskID)
		createdTasks = append(createdTasks, CreatedTask{
			TaskScopeID: taskScopeID,
			TaskNumber:  taskNumber,
			Title:       input.Title,
		})
	}

	tasks, err := txs.listTasksByIDsForQueryer(ctx, tx, workspaceID, taskIDs)
	if err != nil {
		return nil, nil, err
	}
	return tasks, createdTasks, nil
}
