package privateagent

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

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

const taskActiveTargetScopeThreadFocus = "thread_focus"

type taskActiveTargetRecord = message.TaskActiveTargetRecord

// RequireTaskActiveTargetAgentForUser verifies that an agent-session active
// target can only be persisted for an agent owned by the user and currently
// participating in the target task scope.
func (store *Store) RequireTaskActiveTargetAgentForUser(ctx context.Context, userID string, workspaceID string, taskScopeID string, agentID string) error {
	if store == nil || store.db == nil {
		return fmt.Errorf("private agent projection store is not configured")
	}
	membership, err := store.loadHumanWorkspaceMember(ctx, store.db, userID, workspaceID)
	if err != nil {
		return err
	}
	status, err := loadTaskNotificationScopeStatus(ctx, store.db, workspaceID, taskScopeID)
	if err != nil {
		return err
	}
	if strings.TrimSpace(status) != "active" {
		return badRequest("task scope is archived")
	}
	agent, err := store.loadAgentEntryOwner(ctx, store.db, workspaceID, agentID)
	if err != nil {
		return err
	}
	if strings.TrimSpace(agent.OwnerMemberID) != strings.TrimSpace(membership.Member.ID) {
		return ErrForbidden
	}
	found, err := taskAgentParticipatesInTaskScope(ctx, store.db, taskScopeID, membership.Member.ID, agent.AgentMemberID)
	if err != nil {
		return err
	}
	if !found {
		return ErrNotFound
	}
	return nil
}

func (store *Store) taskActiveTargetForMessageTx(ctx context.Context, tx coredb.QueryRower, workspaceID string, target message.ThreadAccess, messageType string) (message.ReceiveActiveTaskTarget, error) {
	if strings.TrimSpace(messageType) != "" {
		return message.ReceiveActiveTaskTarget{}, nil
	}
	targetKey := appChannelIDForTarget(target)
	if targetKey == "" {
		return message.ReceiveActiveTaskTarget{}, nil
	}
	activeTarget, found, err := store.loadTaskActiveTarget(ctx, tx, workspaceID, taskActiveTargetScopeThreadFocus, targetKey)
	if err != nil || !found {
		return message.ReceiveActiveTaskTarget{}, err
	}
	return receiveActiveTaskTargetFromRecord(activeTarget).Normalize(), nil
}

func (store *Store) loadTaskActiveTarget(ctx context.Context, queryer coredb.QueryRower, workspaceID string, scope string, targetKey string) (taskActiveTargetRecord, bool, error) {
	normalizedWorkspaceID := strings.TrimSpace(workspaceID)
	normalizedScope := strings.TrimSpace(scope)
	normalizedTargetKey := strings.TrimSpace(targetKey)
	if normalizedWorkspaceID == "" || normalizedScope == "" || normalizedTargetKey == "" {
		return taskActiveTargetRecord{}, false, nil
	}
	var record taskActiveTargetRecord
	if err := queryer.QueryRow(ctx, `
SELECT
  tat.workspace_id, tat.scope, tat.target_key, tat.task_id, tat.task_channel_id,
	  CASE WHEN COALESCE(p.slug, '') != '' THEN CONCAT('#', p.slug) ELSE tat.task_channel_id END,
  tat.task_number,
  tat.task_title, tat.source, tat.thread_target, COALESCE(tat.agent_id, ''), tat.role, tat.root_task_number,
  tat.parent_task_ref, tat.selected_by_user_id, tat.created_at, tat.updated_at
FROM task_active_targets tat
LEFT JOIN scope_channel_profiles p ON p.scope_id = tat.task_channel_id AND p.workspace_id = tat.workspace_id
WHERE tat.workspace_id = $1 AND tat.scope = $2 AND tat.target_key = $3
`, normalizedWorkspaceID, normalizedScope, normalizedTargetKey).Scan(
		&record.WorkspaceID,
		&record.Scope,
		&record.TargetKey,
		&record.TaskID,
		&record.TaskChannelID,
		&record.TaskChannelRef,
		&record.TaskNumber,
		&record.TaskTitle,
		&record.Source,
		&record.ThreadTarget,
		&record.AgentID,
		&record.Role,
		&record.RootTaskNumber,
		&record.ParentTaskRef,
		&record.SelectedByUserID,
		&record.CreatedAt,
		&record.UpdatedAt,
	); err != nil {
		if coredb.IsNotFound(err) {
			return taskActiveTargetRecord{}, false, nil
		}
		return taskActiveTargetRecord{}, false, fmt.Errorf("load task active target: %w", err)
	}
	return record, true, nil
}

// SetTaskActiveTargetForUser persists the current task-routing hint for a
// thread or agent-session surface. The caller owns task authorization; this
// method keeps the projection table consistent for agent-runtime wake delivery.
func (store *Store) SetTaskActiveTargetForUser(ctx context.Context, userID string, workspaceID string, target message.TaskActiveTargetRecord) (message.TaskActiveTargetRecord, error) {
	if store == nil || store.db == nil {
		return message.TaskActiveTargetRecord{}, fmt.Errorf("private agent projection store is not configured")
	}
	record := normalizeTaskActiveTargetRecord(userID, workspaceID, target)
	if record.WorkspaceID == "" {
		return message.TaskActiveTargetRecord{}, badRequest("workspace_id is required")
	}
	if record.Scope == "" {
		return message.TaskActiveTargetRecord{}, badRequest("active target scope is required")
	}
	if record.TargetKey == "" {
		return message.TaskActiveTargetRecord{}, badRequest("active target key is required")
	}
	if record.TaskID == "" {
		return message.TaskActiveTargetRecord{}, badRequest("task_id is required")
	}
	if record.TaskChannelID == "" {
		return message.TaskActiveTargetRecord{}, badRequest("task channel is required")
	}
	if record.SelectedByUserID == "" {
		return message.TaskActiveTargetRecord{}, badRequest("selected user is required")
	}
	now := time.Now().UTC()
	_, err := store.db.Exec(ctx, `
INSERT INTO task_active_targets (
  workspace_id, scope, target_key, task_id, task_channel_id, task_number, task_title,
  source, thread_target, agent_id, role, root_task_number, parent_task_ref,
  selected_by_user_id, created_at, updated_at
) VALUES (
  $1, $2, $3, $4, $5, $6, $7,
  $8, $9, NULLIF($10, ''), $11, $12, $13,
  $14, $15, $15
)
ON DUPLICATE KEY UPDATE
  task_id = VALUES(task_id),
  task_channel_id = VALUES(task_channel_id),
  task_number = VALUES(task_number),
  task_title = VALUES(task_title),
  source = VALUES(source),
  thread_target = VALUES(thread_target),
  agent_id = VALUES(agent_id),
  role = VALUES(role),
  root_task_number = VALUES(root_task_number),
  parent_task_ref = VALUES(parent_task_ref),
  selected_by_user_id = VALUES(selected_by_user_id),
  updated_at = VALUES(updated_at)
`, record.WorkspaceID, record.Scope, record.TargetKey, record.TaskID, record.TaskChannelID, record.TaskNumber, record.TaskTitle,
		record.Source, record.ThreadTarget, record.AgentID, record.Role, record.RootTaskNumber, record.ParentTaskRef,
		record.SelectedByUserID, now)
	if err != nil {
		return message.TaskActiveTargetRecord{}, fmt.Errorf("upsert task active target: %w", err)
	}
	loaded, found, err := store.loadTaskActiveTarget(ctx, store.db, record.WorkspaceID, record.Scope, record.TargetKey)
	if err != nil {
		return message.TaskActiveTargetRecord{}, err
	}
	if !found {
		return message.TaskActiveTargetRecord{}, fmt.Errorf("load upserted task active target: not found")
	}
	return loaded, nil
}

// ListTaskActiveTargetsForUser returns persisted task-routing hints for the
// requested task IDs in the workspace.
func (store *Store) ListTaskActiveTargetsForUser(ctx context.Context, userID string, workspaceID string, taskIDs []string) ([]message.TaskActiveTargetRecord, error) {
	_ = userID
	if store == nil || store.db == nil {
		return nil, fmt.Errorf("private agent projection store is not configured")
	}
	normalizedWorkspaceID := strings.TrimSpace(workspaceID)
	normalizedTaskIDs := normalizeNonEmptyStrings(taskIDs)
	if normalizedWorkspaceID == "" || len(normalizedTaskIDs) == 0 {
		return []message.TaskActiveTargetRecord{}, nil
	}
	taskIDCTE, taskIDArgs, _, err := coredb.BuildStringValuesCTE("requested_tasks", "task_id", normalizedTaskIDs, 2)
	if err != nil {
		return nil, fmt.Errorf("build task active target task ids: %w", err)
	}
	query := fmt.Sprintf(`
WITH %s
SELECT
  tat.workspace_id, tat.scope, tat.target_key, tat.task_id, tat.task_channel_id,
	  CASE WHEN COALESCE(p.slug, '') != '' THEN CONCAT('#', p.slug) ELSE tat.task_channel_id END,
  tat.task_number,
  tat.task_title, tat.source, tat.thread_target, COALESCE(tat.agent_id, ''), tat.role, tat.root_task_number,
  tat.parent_task_ref, tat.selected_by_user_id, tat.created_at, tat.updated_at
FROM task_active_targets tat
JOIN requested_tasks rt ON rt.task_id = tat.task_id
LEFT JOIN scope_channel_profiles p ON p.scope_id = tat.task_channel_id AND p.workspace_id = tat.workspace_id
WHERE tat.workspace_id = $1
ORDER BY tat.updated_at DESC, tat.scope, tat.target_key
`, taskIDCTE)
	args := append([]any{normalizedWorkspaceID}, taskIDArgs...)
	rows, err := store.db.Query(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("list task active targets: %w", err)
	}
	defer rows.Close()

	records := make([]message.TaskActiveTargetRecord, 0)
	for rows.Next() {
		var record message.TaskActiveTargetRecord
		if err := rows.Scan(
			&record.WorkspaceID,
			&record.Scope,
			&record.TargetKey,
			&record.TaskID,
			&record.TaskChannelID,
			&record.TaskChannelRef,
			&record.TaskNumber,
			&record.TaskTitle,
			&record.Source,
			&record.ThreadTarget,
			&record.AgentID,
			&record.Role,
			&record.RootTaskNumber,
			&record.ParentTaskRef,
			&record.SelectedByUserID,
			&record.CreatedAt,
			&record.UpdatedAt,
		); err != nil {
			return nil, fmt.Errorf("scan task active target: %w", err)
		}
		records = append(records, record)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate task active targets: %w", err)
	}
	return records, nil
}

// ClearTaskActiveTargetForUser removes a persisted task-routing hint for a
// thread or agent-session surface.
func (store *Store) ClearTaskActiveTargetForUser(ctx context.Context, userID string, workspaceID string, scope string, targetKey string) error {
	_ = userID
	if store == nil || store.db == nil {
		return fmt.Errorf("private agent projection store is not configured")
	}
	normalizedWorkspaceID := strings.TrimSpace(workspaceID)
	normalizedScope := strings.TrimSpace(scope)
	normalizedTargetKey := strings.TrimSpace(targetKey)
	if normalizedWorkspaceID == "" || normalizedScope == "" || normalizedTargetKey == "" {
		return nil
	}
	if _, err := store.db.Exec(ctx, `
DELETE FROM task_active_targets
WHERE workspace_id = $1 AND scope = $2 AND target_key = $3
`, normalizedWorkspaceID, normalizedScope, normalizedTargetKey); err != nil {
		return fmt.Errorf("clear task active target: %w", err)
	}
	return nil
}

func normalizeTaskActiveTargetRecord(userID string, workspaceID string, target message.TaskActiveTargetRecord) message.TaskActiveTargetRecord {
	target.WorkspaceID = strings.TrimSpace(target.WorkspaceID)
	if target.WorkspaceID == "" {
		target.WorkspaceID = strings.TrimSpace(workspaceID)
	}
	target.Scope = strings.TrimSpace(target.Scope)
	target.TargetKey = strings.TrimSpace(target.TargetKey)
	target.TaskID = strings.TrimSpace(target.TaskID)
	target.TaskChannelID = strings.TrimSpace(target.TaskChannelID)
	target.TaskTitle = strings.TrimSpace(target.TaskTitle)
	target.Source = strings.TrimSpace(target.Source)
	target.ThreadTarget = strings.TrimSpace(target.ThreadTarget)
	target.AgentID = strings.TrimSpace(target.AgentID)
	target.Role = strings.TrimSpace(target.Role)
	target.ParentTaskRef = strings.TrimSpace(target.ParentTaskRef)
	target.SelectedByUserID = strings.TrimSpace(target.SelectedByUserID)
	if target.SelectedByUserID == "" {
		target.SelectedByUserID = strings.TrimSpace(userID)
	}
	if target.RootTaskNumber <= 0 {
		target.RootTaskNumber = target.TaskNumber
	}
	return target
}

func receiveActiveTaskTargetFromRecord(record taskActiveTargetRecord) message.ReceiveActiveTaskTarget {
	channel := strings.TrimSpace(record.TaskChannelRef)
	if channel == "" {
		channel = record.TaskChannelID
	}
	return message.ReceiveActiveTaskTarget{
		Channel:        channel,
		TaskNumber:     record.TaskNumber,
		Source:         record.Source,
		ThreadTarget:   record.ThreadTarget,
		Role:           record.Role,
		RootTaskNumber: record.RootTaskNumber,
	}
}

func appChannelIDForTarget(target message.ThreadAccess) string {
	trimmedChannelID := strings.TrimSpace(target.ChannelID)
	if trimmedChannelID != "" {
		return trimmedChannelID
	}
	return strings.TrimSpace(target.DMThreadID)
}
