package diff

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

	"github.com/open-code-review/open-code-review/internal/config/template"
	"github.com/open-code-review/open-code-review/internal/llm"
	"github.com/open-code-review/open-code-review/internal/model"
	"github.com/open-code-review/open-code-review/internal/stdout"
	"github.com/open-code-review/open-code-review/internal/telemetry"
)

// ReLocateComment calls the LLM to regenerate a precise existing_code snippet
// when text-based matching fails, then retries ResolveComment with the new snippet.
// Returns (success, response, requestMessages) so the caller can record session
// history and track token usage. Response and messages are nil on early exits.
func ReLocateComment(
	ctx context.Context,
	cm *model.LlmComment,
	d *model.Diff,
	client llm.LLMClient,
	task *template.LlmConversation,
	modelName string,
	maxTokens int,
) (bool, *llm.ChatResponse, []llm.Message) {
	if task == nil || len(task.Messages) == 0 {
		return false, nil, nil
	}

	if task.Timeout > 0 {
		var cancel context.CancelFunc
		ctx, cancel = context.WithTimeout(ctx, time.Duration(task.Timeout)*time.Second)
		defer cancel()
	}

	messages := make([]llm.Message, 0, len(task.Messages))
	for _, m := range task.Messages {
		content := m.Content
		content = strings.ReplaceAll(content, "{diff}", d.Diff)
		content = strings.ReplaceAll(content, "{existing_code}", cm.ExistingCode)
		content = strings.ReplaceAll(content, "{suggestion_content}", cm.Content)
		messages = append(messages, llm.NewTextMessage(m.Role, content))
	}

	startTime := time.Now()
	_, llmSpan := telemetry.StartLLMSpan(ctx, modelName)
	resp, err := client.CompletionsWithCtx(ctx, llm.ChatRequest{
		Model:     modelName,
		Messages:  messages,
		MaxTokens: maxTokens,
	})
	duration := time.Since(startTime)
	if err != nil {
		telemetry.RecordLLMResult(llmSpan, duration, 0, err)
		llmSpan.End()
		fmt.Fprintf(stdout.Writer(), "[ocr] Re-location LLM call failed for %s: %v\n", cm.Path, err)
		return false, nil, messages
	}
	var totalTokens int64
	if resp.Usage != nil {
		totalTokens = resp.Usage.TotalTokens
	}
	telemetry.RecordLLMResult(llmSpan, duration, totalTokens, nil)
	llmSpan.End()

	code := extractCodeBlock(resp.Content())
	if code == "" {
		return false, resp, messages
	}

	original := cm.ExistingCode
	cm.ExistingCode = code
	if ResolveComment(cm, d) {
		return true, resp, messages
	}
	cm.ExistingCode = original
	return false, resp, messages
}

// extractCodeBlock extracts the content of the first fenced code block from text.
// Returns empty string if no code block is found.
func extractCodeBlock(text string) string {
	text = strings.TrimSpace(text)
	start := strings.Index(text, "```")
	if start < 0 {
		return ""
	}
	afterOpen := start + 3
	// Skip optional language tag on the opening fence line.
	if nl := strings.IndexByte(text[afterOpen:], '\n'); nl >= 0 {
		afterOpen += nl + 1
	} else {
		return ""
	}
	end := strings.Index(text[afterOpen:], "```")
	if end < 0 {
		return ""
	}
	return strings.TrimSpace(text[afterOpen : afterOpen+end])
}
