package knowledgeqa

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

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

func (store *Store) ListTurnsForOwner(ctx context.Context, workspaceID, ownerUserID, threadChannelID string, limit int) ([]Turn, error) {
	if store == nil || store.db == nil {
		return nil, fmt.Errorf("knowledge Q&A store is not configured")
	}
	workspaceID = strings.TrimSpace(workspaceID)
	ownerUserID = strings.TrimSpace(ownerUserID)
	threadChannelID = strings.TrimSpace(threadChannelID)
	if workspaceID == "" || ownerUserID == "" || threadChannelID == "" {
		return nil, ErrNotFound
	}
	if limit <= 0 || limit > 200 {
		limit = 100
	}
	var found int
	if err := store.db.QueryRow(ctx, `
SELECT COUNT(*) FROM knowledge_qa_sessions
WHERE workspace_id=$1 AND owner_user_id=$2 AND thread_channel_id=$3
`, workspaceID, ownerUserID, threadChannelID).Scan(&found); err != nil {
		return nil, fmt.Errorf("authorize Knowledge Q&A history: %w", err)
	}
	if found != 1 {
		return nil, ErrNotFound
	}
	rows, err := store.db.Query(ctx, turnSelectSQL()+`
FROM knowledge_qa_turns AS turn
JOIN knowledge_qa_sessions AS session ON session.id=turn.session_id
WHERE turn.workspace_id=$1 AND session.owner_user_id=$2
  AND session.thread_channel_id=$3
ORDER BY turn.queued_at ASC, turn.id ASC
LIMIT $4
`, workspaceID, ownerUserID, threadChannelID, limit)
	if err != nil {
		return nil, fmt.Errorf("list Knowledge Q&A turns: %w", err)
	}
	defer rows.Close()
	turns := make([]Turn, 0)
	for rows.Next() {
		turn, err := scanTurn(rows)
		if err != nil {
			return nil, err
		}
		turns = append(turns, turn)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate Knowledge Q&A turns: %w", err)
	}
	if len(turns) == 0 {
		return turns, nil
	}
	activities, err := store.listEventsForOwner(ctx, workspaceID, ownerUserID, threadChannelID, limit)
	if err != nil {
		return nil, err
	}
	turnByID := make(map[string]*Turn, len(turns))
	for index := range turns {
		turnByID[turns[index].ID] = &turns[index]
	}
	for _, item := range activities {
		if turn := turnByID[item.turnID]; turn != nil {
			turn.Activities = append(turn.Activities, item.activity)
		}
	}
	debugCandidate := &turns[len(turns)-1]
	if debugCandidate.Status == "failed" {
		if err := store.db.QueryRow(ctx, `
SELECT COALESCE(turn.debug_model_output,'')
FROM knowledge_qa_turns AS turn
JOIN knowledge_qa_sessions AS session ON session.id=turn.session_id
WHERE turn.workspace_id=$1 AND session.owner_user_id=$2
  AND session.thread_channel_id=$3 AND turn.id=$4
  AND NOT EXISTS (
    SELECT 1
    FROM knowledge_qa_turns AS later
    WHERE later.workspace_id=turn.workspace_id
      AND later.session_id=turn.session_id
      AND (later.queued_at > turn.queued_at OR
           (later.queued_at=turn.queued_at AND later.id > turn.id))
  )
`, workspaceID, ownerUserID, threadChannelID, debugCandidate.ID).Scan(&debugCandidate.DebugModelOutput); err != nil {
			if coredb.IsNotFound(err) {
				// The candidate is no longer eligible or present, so history
				// remains available without projecting sensitive debug output.
				return turns, nil
			}
			return nil, fmt.Errorf("load latest failed Knowledge Q&A debug output: %w", err)
		}
	}
	return turns, nil
}

// ReadRuntimeContext returns the owner/session/current-turn-bound runtime
// context. The persisted summary is derived state; the uncompacted tail is
// rebuilt from canonical terminal turns and formal assistant messages.
func (store *Store) ReadRuntimeContext(ctx context.Context, workspaceID, ownerUserID, sessionID, currentTurnID string) (RuntimeContext, error) {
	if store == nil || store.db == nil {
		return RuntimeContext{}, fmt.Errorf("knowledge Q&A store is not configured")
	}
	workspaceID = strings.TrimSpace(workspaceID)
	ownerUserID = strings.TrimSpace(ownerUserID)
	sessionID = strings.TrimSpace(sessionID)
	currentTurnID = strings.TrimSpace(currentTurnID)
	var result RuntimeContext
	var currentQueuedAt time.Time
	if err := store.db.QueryRow(ctx, `
SELECT turn.queued_at,
       COALESCE(runtime_context.generation,0),
       COALESCE(runtime_context.summary,''),
       COALESCE(runtime_context.compacted_through_turn_id,''),
       COALESCE(runtime_context.summary_version,'')
FROM knowledge_qa_turns AS turn
JOIN knowledge_qa_sessions AS session ON session.id=turn.session_id
LEFT JOIN knowledge_qa_runtime_contexts AS runtime_context
  ON runtime_context.session_id=session.id AND runtime_context.workspace_id=session.workspace_id
WHERE turn.workspace_id=$1 AND turn.id=$2 AND turn.session_id=$3
  AND session.workspace_id=$1 AND session.owner_user_id=$4
`, workspaceID, currentTurnID, sessionID, ownerUserID).Scan(
		&currentQueuedAt, &result.Generation, &result.Summary,
		&result.CompactedThroughTurnID, &result.SummaryVersion,
	); err != nil {
		if coredb.IsNotFound(err) {
			return RuntimeContext{}, ErrNotFound
		}
		return RuntimeContext{}, fmt.Errorf("authorize Knowledge Q&A runtime context: %w", err)
	}
	if result.CompactedThroughTurnID == "" {
		// A summary without a valid canonical boundary is not usable. Rebuild
		// from canonical turns and let the next compaction replace it by CAS.
		result.Summary = ""
		result.SummaryVersion = ""
	}
	rows, err := store.db.Query(ctx, `
SELECT turn.id, turn.status, turn.question,
       CASE WHEN turn.status='completed' THEN COALESCE(message.content,'') ELSE '' END
FROM knowledge_qa_turns AS turn
LEFT JOIN scope_messages AS message
  ON message.workspace_id=turn.workspace_id AND message.id=turn.assistant_message_id
LEFT JOIN knowledge_qa_turns AS boundary
  ON boundary.workspace_id=turn.workspace_id AND boundary.session_id=turn.session_id AND boundary.id=$5
WHERE turn.workspace_id=$1 AND turn.session_id=$2
  AND turn.status IN ('completed','no_answer','failed','canceled')
  AND (turn.queued_at < $3 OR (turn.queued_at=$3 AND turn.id < $4))
  AND ($5='' OR boundary.id IS NOT NULL AND
       (turn.queued_at > boundary.queued_at OR
        (turn.queued_at=boundary.queued_at AND turn.id > boundary.id)))
ORDER BY turn.queued_at ASC, turn.id ASC
`, workspaceID, sessionID, currentQueuedAt, currentTurnID, result.CompactedThroughTurnID)
	if err != nil {
		return RuntimeContext{}, fmt.Errorf("list Knowledge Q&A runtime context: %w", err)
	}
	defer rows.Close()
	result.Turns = make([]RuntimeHistoryTurn, 0)
	result.Bytes = len(result.Summary)
	for rows.Next() {
		var turn RuntimeHistoryTurn
		if err := rows.Scan(&turn.TurnID, &turn.Status, &turn.Question, &turn.Answer); err != nil {
			return RuntimeContext{}, fmt.Errorf("scan Knowledge Q&A runtime context: %w", err)
		}
		turn.TurnID = strings.TrimSpace(turn.TurnID)
		turn.Status = strings.TrimSpace(turn.Status)
		turn.Question = strings.TrimSpace(turn.Question)
		turn.Answer = strings.TrimSpace(turn.Answer)
		result.Bytes += len(turn.Question) + len(turn.Answer)
		result.Turns = append(result.Turns, turn)
	}
	if err := rows.Err(); err != nil {
		return RuntimeContext{}, fmt.Errorf("iterate Knowledge Q&A runtime context: %w", err)
	}
	return result, nil
}

type turnActivityRow struct {
	turnID   string
	activity protocol.RuntimeActivity
}

func (store *Store) listEventsForOwner(ctx context.Context, workspaceID, ownerUserID, threadChannelID string, limit int) ([]turnActivityRow, error) {
	rows, err := store.db.Query(ctx, `
SELECT event.turn_id, event.sequence, event.kind, event.status,
       event.summary, event.tool_name, event.payload_json,
       event.created_at
FROM knowledge_qa_events AS event
JOIN (
  SELECT turn.id, turn.current_attempt_id, turn.queued_at
  FROM knowledge_qa_turns AS turn
  JOIN knowledge_qa_sessions AS session ON session.id=turn.session_id
  WHERE turn.workspace_id=$1 AND session.owner_user_id=$2
    AND session.thread_channel_id=$3
  ORDER BY turn.queued_at ASC, turn.id ASC
  LIMIT $4
) AS selected_turn ON selected_turn.id=event.turn_id
  AND selected_turn.current_attempt_id=event.attempt_id
ORDER BY selected_turn.queued_at ASC, event.sequence ASC
`, workspaceID, ownerUserID, threadChannelID, limit)
	if err != nil {
		return nil, fmt.Errorf("list Knowledge Q&A events: %w", err)
	}
	defer rows.Close()
	result := make([]turnActivityRow, 0)
	for rows.Next() {
		var item turnActivityRow
		var payload []byte
		if err := rows.Scan(&item.turnID, &item.activity.Sequence,
			&item.activity.Kind, &item.activity.Status, &item.activity.Summary,
			&item.activity.ToolName, &payload,
			&item.activity.CreatedAt); err != nil {
			return nil, fmt.Errorf("scan Knowledge Q&A event: %w", err)
		}
		projectRuntimeActivityPayload(&item.activity, payload)
		item.activity.ID = fmt.Sprintf("%s:%d", item.turnID, item.activity.Sequence)
		item.activity.UpdatedAt = item.activity.CreatedAt
		result = append(result, item)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate Knowledge Q&A events: %w", err)
	}
	return result, nil
}
