package taskboard

import (
	"context"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"

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

type taskBrowseScope struct {
	WorkspaceID        string
	MemberID           string
	ChannelID          string
	ActorPrincipalType PrincipalType
	Query              TaskBrowseQuery
	SearchApplied      bool
	SearchTaskIDs      []string
}

var taskBrowseBoardColumnOrder = []TaskBrowseColumnKey{
	TaskBrowseColumnTodo,
	TaskBrowseColumnInProgress,
	TaskBrowseColumnInReview,
	TaskBrowseColumnDone,
}

func (txs TxStore) BrowseWorkspaceRowsForUser(ctx context.Context, userID string, workspaceID string, query TaskBrowseQuery) (TaskRowBrowsePage, error) {
	workspaceMember, err := access.LoadHumanWorkspaceMember(ctx, txs.db, userID, workspaceID)
	if errors.Is(err, access.ErrForbidden) {
		return TaskRowBrowsePage{}, ErrForbidden
	}
	if err != nil {
		return TaskRowBrowsePage{}, fmt.Errorf("load task workspace member: %w", err)
	}
	return txs.browseTaskRows(ctx, taskBrowseScope{
		WorkspaceID: workspaceID,
		MemberID:    workspaceMember.Member.ID,
		Query:       query,
	})
}

func (txs TxStore) BrowseWorkspaceBoardForUser(ctx context.Context, userID string, workspaceID string, query TaskBrowseQuery, columnCursors map[string]TaskBrowseCursor, countFirstDone bool) (TaskBoardBrowsePage, error) {
	workspaceMember, err := access.LoadHumanWorkspaceMember(ctx, txs.db, userID, workspaceID)
	if errors.Is(err, access.ErrForbidden) {
		return TaskBoardBrowsePage{}, ErrForbidden
	}
	if err != nil {
		return TaskBoardBrowsePage{}, fmt.Errorf("load task workspace member: %w", err)
	}

	return txs.browseTaskBoard(ctx, taskBrowseScope{
		WorkspaceID:        workspaceID,
		MemberID:           workspaceMember.Member.ID,
		ActorPrincipalType: PrincipalTypeUser,
		Query:              query,
	}, columnCursors, countFirstDone)
}

func (txs TxStore) BrowseChannelRowsForUser(ctx context.Context, userID string, workspaceID string, channelID string, query TaskBrowseQuery) (TaskRowBrowsePage, error) {
	channelAccess, err := txs.loadAuthorizedUserTaskChannelAccess(ctx, txs.db, userID, workspaceID, channelID, taskReadChannelAction)
	if err != nil {
		return TaskRowBrowsePage{}, err
	}
	return txs.browseTaskRows(ctx, taskBrowseScope{
		WorkspaceID: workspaceID,
		MemberID:    channelAccess.WorkspaceMember.Member.ID,
		ChannelID:   channelAccess.Channel.ChannelID,
		Query:       query,
	})
}

func (txs TxStore) BrowseChannelRowsForAgent(ctx context.Context, agent AgentActor, board BoardRef, query TaskBrowseQuery) (TaskRowBrowsePage, error) {
	channelID, err := txs.resolveAccessibleAgentBoard(ctx, txs.db, agent, board)
	if err != nil {
		return TaskRowBrowsePage{}, err
	}
	return txs.browseChannelRowsForAgentChannelID(ctx, agent, channelID, query)
}

func (txs TxStore) browseChannelRowsForAgentChannelID(ctx context.Context, agent AgentActor, channelID string, query TaskBrowseQuery) (TaskRowBrowsePage, error) {
	return txs.browseTaskRows(ctx, taskBrowseScope{
		WorkspaceID: agent.WorkspaceID,
		MemberID:    agent.MemberID,
		ChannelID:   channelID,
		Query:       query,
	})
}

func (txs TxStore) BrowseChannelBoardForUser(ctx context.Context, userID string, workspaceID string, channelID string, query TaskBrowseQuery, columnCursors map[string]TaskBrowseCursor, countFirstDone bool) (TaskBoardBrowsePage, error) {
	channelAccess, err := txs.loadAuthorizedUserTaskChannelAccess(ctx, txs.db, userID, workspaceID, channelID, taskReadChannelAction)
	if err != nil {
		return TaskBoardBrowsePage{}, err
	}

	scope := taskBrowseScope{
		WorkspaceID:        workspaceID,
		MemberID:           channelAccess.WorkspaceMember.Member.ID,
		ChannelID:          channelAccess.Channel.ChannelID,
		ActorPrincipalType: PrincipalTypeUser,
		Query:              query,
	}

	return txs.browseTaskBoard(ctx, scope, columnCursors, countFirstDone)
}

func (txs TxStore) browseTaskBoard(ctx context.Context, scope taskBrowseScope, columnCursors map[string]TaskBrowseCursor, countFirstDone bool) (TaskBoardBrowsePage, error) {
	var emptySearch bool
	var err error
	scope, emptySearch, err = txs.applytaskSearchPlan(ctx, scope)
	if err != nil {
		return TaskBoardBrowsePage{}, err
	}
	layout, err := txs.loadTaskBoardLayoutForBrowse(ctx, scope)
	if err != nil {
		return TaskBoardBrowsePage{}, err
	}
	if emptySearch {
		return emptyTaskBoardBrowsePage(scope, layout), nil
	}

	columnCounts, totalCount, err := txs.browseTaskBoardCounts(ctx, scope, layout)
	if err != nil {
		return TaskBoardBrowsePage{}, err
	}

	page := TaskBoardBrowsePage{
		QueryEcho:         scope.Query,
		TotalCount:        totalCount,
		ColumnOrder:       append([]string(nil), layout.ColumnOrder...),
		ColumnDefinitions: append([]TaskBoardColumnDefinition(nil), layout.ColumnDefinitions...),
		ColumnCounts:      make(map[string]int64, len(layout.ColumnOrder)),
		Columns:           make(map[string]TaskBoardColumnPage, len(layout.ColumnOrder)),
	}
	for _, columnKey := range layout.ColumnOrder {
		count := columnCounts[columnKey]
		page.ColumnCounts[columnKey] = count

		if !shouldFetchTaskBoardColumn(columnKey, columnCursors) {
			page.Columns[columnKey] = taskBoardColumnMetadataPage(count)
			continue
		}

		columnPage, err := txs.browseTaskBoardColumn(ctx, scope, layout, columnKey, columnCursors[columnKey], count)
		if err != nil {
			return TaskBoardBrowsePage{}, err
		}
		page.Columns[columnKey] = columnPage
	}

	return page, nil
}

func shouldFetchTaskBoardColumn(columnName string, columnCursors map[string]TaskBrowseCursor) bool {
	if len(columnCursors) == 0 {
		return true
	}
	_, ok := columnCursors[columnName]
	return ok
}

func taskBoardColumnMetadataPage(count int64) TaskBoardColumnPage {
	return TaskBoardColumnPage{
		Tasks:   []Task{},
		Count:   count,
		HasMore: count > 0,
	}
}

func (txs TxStore) browseTaskRows(ctx context.Context, scope taskBrowseScope) (TaskRowBrowsePage, error) {
	cursor, err := normalizeTaskBrowseCursor(scope.Query.Cursor, scope.Query.SortMode)
	if err != nil {
		return TaskRowBrowsePage{}, err
	}
	var emptySearch bool
	scope, emptySearch, err = txs.applytaskSearchPlan(ctx, scope)
	if err != nil {
		return TaskRowBrowsePage{}, err
	}
	if emptySearch {
		return TaskRowBrowsePage{
			QueryEcho:    scope.Query,
			Rows:         []Task{},
			TotalCount:   0,
			HasMore:      false,
			NextCursor:   TaskBrowseCursor{},
			ColumnCounts: map[string]int64{},
		}, nil
	}

	countQuery, countArgs, err := buildTaskBrowseCountQuery(scope)
	if err != nil {
		return TaskRowBrowsePage{}, err
	}
	var totalCount int64
	if err := txs.db.QueryRow(ctx, countQuery, countArgs...).Scan(&totalCount); err != nil {
		return TaskRowBrowsePage{}, fmt.Errorf("count browse rows: %w", err)
	}

	rowsQuery, rowsArgs, err := buildTaskBrowseRowsQuery(scope, cursor)
	if err != nil {
		return TaskRowBrowsePage{}, err
	}
	rows, err := txs.db.Query(ctx, rowsQuery, rowsArgs...)
	if err != nil {
		return TaskRowBrowsePage{}, fmt.Errorf("browse tasks: %w", err)
	}
	defer rows.Close()

	tasks := make([]Task, 0, scope.Query.Limit+1)
	for rows.Next() {
		task, err := scanTask(rows)
		if err != nil {
			return TaskRowBrowsePage{}, err
		}
		tasks = append(tasks, task)
	}
	if err := rows.Err(); err != nil {
		return TaskRowBrowsePage{}, fmt.Errorf("iterate browse rows: %w", err)
	}

	tasks, err = txs.normalizeTasksWithTaxonomy(ctx, txs.db, scope.WorkspaceID, tasks)
	if err != nil {
		return TaskRowBrowsePage{}, err
	}

	hasMore := len(tasks) > scope.Query.Limit
	if hasMore {
		tasks = tasks[:scope.Query.Limit]
	}

	var nextCursor TaskBrowseCursor
	if hasMore && len(tasks) > 0 {
		nextCursor, err = buildTaskBrowseNextCursor(scope.Query.SortMode, tasks[len(tasks)-1])
		if err != nil {
			return TaskRowBrowsePage{}, err
		}
	}

	return TaskRowBrowsePage{
		QueryEcho:    scope.Query,
		Rows:         tasks,
		TotalCount:   totalCount,
		HasMore:      hasMore,
		NextCursor:   nextCursor,
		ColumnCounts: map[string]int64{},
	}, nil
}

func (txs TxStore) applytaskSearchPlan(ctx context.Context, scope taskBrowseScope) (taskBrowseScope, bool, error) {
	searchQuery := strings.TrimSpace(scope.Query.SearchQuery)
	if searchQuery == "" {
		scope.SearchApplied = false
		scope.SearchTaskIDs = nil
		return scope, false, nil
	}
	result, err := searchTaskProfileIDs(ctx, txs.db, taskSearchPlan{
		WorkspaceID: scope.WorkspaceID,
		MemberID:    scope.MemberID,
		ChannelID:   scope.ChannelID,
		Query:       searchQuery,
	})
	if err != nil {
		return scope, false, err
	}
	scope.Query.SearchQuery = result.NormalizedQuery
	scope.SearchApplied = true
	scope.SearchTaskIDs = result.TaskIDs
	return scope, len(result.TaskIDs) == 0, nil
}

func emptyTaskBoardBrowsePage(scope taskBrowseScope, layout taskBoardLayout) TaskBoardBrowsePage {
	page := TaskBoardBrowsePage{
		QueryEcho:         scope.Query,
		TotalCount:        0,
		ColumnOrder:       append([]string(nil), layout.ColumnOrder...),
		ColumnDefinitions: append([]TaskBoardColumnDefinition(nil), layout.ColumnDefinitions...),
		ColumnCounts:      make(map[string]int64, len(layout.ColumnOrder)),
		Columns:           make(map[string]TaskBoardColumnPage, len(layout.ColumnOrder)),
	}
	for _, columnKey := range layout.ColumnOrder {
		page.ColumnCounts[columnKey] = 0
		page.Columns[columnKey] = TaskBoardColumnPage{Tasks: []Task{}, Count: 0}
	}
	return page
}

func (txs TxStore) browseTaskBoardCounts(ctx context.Context, scope taskBrowseScope, layout taskBoardLayout) (map[string]int64, int64, error) {
	countQuery, countArgs, err := buildTaskBrowseBoardCountsQuery(scope, layout)
	if err != nil {
		return nil, 0, err
	}
	rows, err := txs.db.Query(ctx, countQuery, countArgs...)
	if err != nil {
		return nil, 0, fmt.Errorf("count board columns: %w", err)
	}
	defer rows.Close()

	columnCounts := make(map[string]int64, len(layout.ColumnOrder))
	for _, columnKey := range layout.ColumnOrder {
		columnCounts[columnKey] = 0
	}

	var totalCount int64
	for rows.Next() {
		var columnKey string
		var count int64
		if err := rows.Scan(&columnKey, &count); err != nil {
			return nil, 0, fmt.Errorf("scan board column count: %w", err)
		}
		columnCounts[columnKey] = count
		totalCount += count
	}
	if err := rows.Err(); err != nil {
		return nil, 0, fmt.Errorf("iterate board column counts: %w", err)
	}

	return columnCounts, totalCount, nil
}

func (txs TxStore) browseTaskBoardColumn(ctx context.Context, scope taskBrowseScope, layout taskBoardLayout, columnKey string, cursor TaskBrowseCursor, count int64) (TaskBoardColumnPage, error) {
	if count == 0 {
		return TaskBoardColumnPage{
			Tasks: []Task{},
			Count: 0,
		}, nil
	}

	rowsQuery, rowsArgs, err := buildTaskBrowseBoardRowsQuery(scope, layout, columnKey, cursor)
	if err != nil {
		return TaskBoardColumnPage{}, err
	}
	rows, err := txs.db.Query(ctx, rowsQuery, rowsArgs...)
	if err != nil {
		return TaskBoardColumnPage{}, fmt.Errorf("browse board column %s: %w", columnKey, err)
	}
	defer rows.Close()

	tasks := make([]Task, 0, scope.Query.Limit+1)
	for rows.Next() {
		task, err := scanTask(rows)
		if err != nil {
			return TaskBoardColumnPage{}, err
		}
		tasks = append(tasks, task)
	}
	if err := rows.Err(); err != nil {
		return TaskBoardColumnPage{}, fmt.Errorf("iterate board column %s: %w", columnKey, err)
	}

	tasks, err = txs.normalizeTasksWithTaxonomy(ctx, txs.db, scope.WorkspaceID, tasks)
	if err != nil {
		return TaskBoardColumnPage{}, err
	}

	hasMore := len(tasks) > scope.Query.Limit
	if hasMore {
		tasks = tasks[:scope.Query.Limit]
	}

	var nextCursor TaskBrowseCursor
	if hasMore && len(tasks) > 0 {
		nextCursor, err = buildTaskBrowseNextCursor(scope.Query.SortMode, tasks[len(tasks)-1])
		if err != nil {
			return TaskBoardColumnPage{}, err
		}
	}

	return TaskBoardColumnPage{
		Tasks:      tasks,
		Count:      count,
		HasMore:    hasMore,
		NextCursor: nextCursor,
	}, nil
}

func buildTaskBrowseNextCursor(sortMode string, task Task) (TaskBrowseCursor, error) {
	normalizedSortMode := normalizeTaskBoardSortMode(sortMode)
	cursor := TaskBrowseCursor{
		SortMode:   normalizedSortMode,
		TaskID:     strings.TrimSpace(task.ID),
		CreatedAt:  task.CreatedAt.UTC(),
		TaskNumber: task.TaskNumber,
	}
	switch normalizedSortMode {
	case "recently_updated":
		cursor.ActivityAt = taskBrowseActivityTime(task)
	case "created_time":
		cursor.ActivityAt = taskBrowseActivityTime(task)
	case "title":
		cursor.Title = strings.ToLower(strings.TrimSpace(task.Title))
	case "priority":
		cursor.PriorityMissing = strings.TrimSpace(task.Priority) == ""
		cursor.PriorityLabel = strings.ToLower(strings.TrimSpace(task.Priority))
	}
	return normalizeTaskBrowseCursor(cursor, normalizedSortMode)
}

func parseTaskBrowseTime(raw string) (time.Time, error) {
	parsed, err := time.Parse(time.RFC3339Nano, strings.TrimSpace(raw))
	if err != nil {
		return time.Time{}, err
	}
	return parsed.UTC(), nil
}

func formatTaskBrowseTime(value time.Time) string {
	return value.UTC().Format(time.RFC3339Nano)
}

func taskBrowseActivityTime(task Task) time.Time {
	if task.LastActivityAt != nil && !task.LastActivityAt.IsZero() {
		return task.LastActivityAt.UTC()
	}
	if task.CompletedAt != nil && !task.CompletedAt.IsZero() {
		return task.CompletedAt.UTC()
	}
	if task.AssignedAt != nil && !task.AssignedAt.IsZero() {
		return task.AssignedAt.UTC()
	}
	return task.CreatedAt.UTC()
}

func taskBrowsePriorityLabel(task Task) string {
	return strings.ToLower(strings.TrimSpace(task.Priority))
}

func taskBrowsePriorityMissing(task Task) bool {
	return taskBrowsePriorityLabel(task) == ""
}

func taskBrowseTaskNumberText(taskNumber int64) string {
	return strconv.FormatInt(taskNumber, 10)
}
