package rewind

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

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

type MessageRecoveryPoint struct {
	WorkspaceID string
	MessageID   string
	AgentID     string
	ThreadID    string
	Strategy    string
	RecoveryRef string
	PreparedAt  time.Time
}

func ScanMessageRecoveryPoints(rows coredb.Rows) ([]MessageRecoveryPoint, error) {
	defer rows.Close()

	points := make([]MessageRecoveryPoint, 0)
	for rows.Next() {
		var point MessageRecoveryPoint
		if err := rows.Scan(
			&point.WorkspaceID,
			&point.MessageID,
			&point.AgentID,
			&point.ThreadID,
			&point.Strategy,
			&point.RecoveryRef,
			&point.PreparedAt,
		); err != nil {
			return nil, err
		}
		points = append(points, point)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return points, nil
}

func WriteMessageRecoveryPointWithExecer(ctx context.Context, execer interface {
	Exec(context.Context, string, ...any) (coredb.Result, error)
}, workspaceID string, messageID string, agentID string, threadID string, strategy string, recoveryRef string, preparedAt time.Time) error {
	trimmedWorkspaceID := strings.TrimSpace(workspaceID)
	trimmedMessageID := strings.TrimSpace(messageID)
	trimmedAgentID := strings.TrimSpace(agentID)
	trimmedThreadID := strings.TrimSpace(threadID)
	trimmedStrategy := strings.TrimSpace(strategy)
	trimmedRecoveryRef := strings.TrimSpace(recoveryRef)
	preparedAt = preparedAt.UTC()

	updateTag, err := execer.Exec(ctx, `
UPDATE message_recovery_points
SET thread_id = $4,
    recovery_strategy = $5,
    recovery_ref = $6,
    prepared_at = $7
WHERE workspace_id = $1
  AND message_id = $2
  AND agent_id = $3
`, trimmedWorkspaceID, trimmedMessageID, trimmedAgentID, trimmedThreadID, trimmedStrategy, trimmedRecoveryRef, preparedAt)
	if err != nil {
		return fmt.Errorf("update message recovery point: %w", err)
	}
	if updateTag.RowsAffected() > 0 {
		return nil
	}

	if _, err := execer.Exec(ctx, `
INSERT INTO message_recovery_points (
    workspace_id,
    message_id,
    agent_id,
    thread_id,
    recovery_strategy,
    recovery_ref,
    prepared_at
) VALUES ($1, $2, $3, $4, $5, $6, $7)
`, trimmedWorkspaceID, trimmedMessageID, trimmedAgentID, trimmedThreadID, trimmedStrategy, trimmedRecoveryRef, preparedAt); err != nil {
		if uniqueViolationMatches(err, "message_recovery_points_pkey", "PRIMARY") {
			_, retryErr := execer.Exec(ctx, `
UPDATE message_recovery_points
SET thread_id = $4,
    recovery_strategy = $5,
    recovery_ref = $6,
    prepared_at = $7
WHERE workspace_id = $1
  AND message_id = $2
  AND agent_id = $3
`, trimmedWorkspaceID, trimmedMessageID, trimmedAgentID, trimmedThreadID, trimmedStrategy, trimmedRecoveryRef, preparedAt)
			return retryErr
		}
		return fmt.Errorf("insert message recovery point: %w", err)
	}
	return nil
}

func BuildDeleteRecoveryPointsByMessageIDsQuery(workspaceID string, agentID string, messageIDs []string) (string, []any, error) {
	trimmedWorkspaceID := strings.TrimSpace(workspaceID)
	trimmedAgentID := strings.TrimSpace(agentID)
	if trimmedWorkspaceID == "" {
		return "", nil, fmt.Errorf("workspace id is required")
	}
	if trimmedAgentID == "" {
		return "", nil, fmt.Errorf("agent id is required")
	}

	cte, messageArgs, _, err := coredb.BuildStringValuesCTE("message_ids", "message_id", messageIDs, 3)
	if err != nil {
		return "", nil, err
	}

	query := fmt.Sprintf(`
WITH %s
DELETE FROM message_recovery_points AS mrp
WHERE mrp.workspace_id = $1
  AND mrp.agent_id = $2
  AND EXISTS (
      SELECT 1
      FROM message_ids
      WHERE message_ids.message_id = mrp.message_id
  )
`, cte)
	args := append([]any{trimmedWorkspaceID, trimmedAgentID}, messageArgs...)
	return query, args, nil
}

func uniqueViolationMatches(err error, constraints ...string) bool {
	actual, ok := coredb.UniqueViolationConstraint(err)
	if !ok {
		return false
	}
	for _, expected := range constraints {
		if actual == strings.TrimSpace(expected) {
			return true
		}
	}
	return false
}
