package agent

import (
	"bufio"
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"strings"
	"time"
)

type LLMClient struct {
	cfg       Config
	apiConfig *APIConfigStore
	client    *http.Client
}

type StreamChunk struct {
	Content      string
	ToolCalls    []ToolCallDelta
	FinishReason string
	Usage        *Usage
	Error        error
}

type ToolCallDelta struct {
	Index          int
	ID             string
	Name           string
	ArgumentsDelta string
}

func NewLLMClient(cfg Config, apiCfg *APIConfigStore) *LLMClient {
	return &LLMClient{
		cfg:       cfg,
		apiConfig: apiCfg,
		client: &http.Client{
			Timeout: cfg.RequestTimeout + 30*time.Second,
		},
	}
}

// isAnthropic 报告当前是否使用 Anthropic Messages API。
func (c *LLMClient) isAnthropic() bool {
	return c.apiConfig.Provider() == ProviderAnthropic
}

func (c *LLMClient) ChatStream(ctx context.Context, messages []Message, tools []ToolDefinition) (<-chan StreamChunk, error) {
	if c.isAnthropic() {
		body := map[string]any{
			"model":       c.apiConfig.Model(),
			"max_tokens":  c.cfg.StreamOutputTokens,
			"stream":      true,
			"temperature": c.cfg.Temperature,
			"messages":    toAnthropicMessages(messages),
		}
		if system := extractSystemPrompt(messages); system != "" {
			body["system"] = system
		}
		if len(tools) > 0 {
			body["tools"] = toAnthropicTools(tools)
		}
		return c.stream(ctx, body)
	}

	body := map[string]any{
		"model":       c.apiConfig.Model(),
		"messages":    toAPIMessages(messages),
		"stream":      true,
		"temperature": c.cfg.Temperature,
		"max_tokens":  c.cfg.StreamOutputTokens,
	}
	if len(tools) > 0 {
		body["tools"] = tools
		body["tool_choice"] = "auto"
	}
	return c.stream(ctx, body)
}

func (c *LLMClient) Compress(ctx context.Context, history []Message) (string, error) {
	systemPrompt := `你是一个高信息密度的对话压缩器。把下面的历史对话压缩成一份中文摘要，供后续继续执行同一任务。
必须保留（按重要程度排序）：
1. 用户的核心目标与最新要求、尚未完成的任务与下一步计划；
2. 已确认的决策与结论、得分/进度（如 TSec 累计分数与已通关/进行中的题目编号）；
3. 关键文件路径、执行过的命令与重要输出要点、代码要点；
4. 错误信息与解决方案、需要继续跟进的问题；
5. 平台提交规则（如 BENCHMARK_TOKEN、unique_code、提交接口与提交方式）。
规则：
- 按时间顺序组织，最新信息优先，可适当合并同类项；
- 若历史包含工具调用（run_bash/read_file/run_coop 等），只保留"做了什么、结果如何"的要点，不要逐字复制命令或输出；
- 不要添加历史中不存在的信息，不要臆测；
- 直接输出摘要正文，不要输出任何解释、标题或多余格式。`
	messages := append([]Message{{Role: "system", Content: &systemPrompt}}, history...)

	if c.isAnthropic() {
		return c.compressAnthropic(ctx, messages)
	}

	body := map[string]any{
		"model":       c.apiConfig.Model(),
		"messages":    toAPIMessages(messages),
		"stream":      false,
		"temperature": 0.2,
		"max_tokens":  c.cfg.CompactionTokens,
	}

	ctx, cancel := context.WithTimeout(ctx, c.cfg.RequestTimeout)
	defer cancel()

	payload, err := json.Marshal(body)
	if err != nil {
		return "", err
	}
	respBody, err := c.doSyncRequestWithRetry(ctx, false, c.apiConfig.BaseURL(), payload)
	if err != nil {
		return "", err
	}

	var result struct {
		Choices []struct {
			Message struct {
				Content          *string `json:"content"`
				ReasoningContent *string `json:"reasoning_content"`
			} `json:"message"`
		} `json:"choices"`
	}
	if err := json.Unmarshal(respBody, &result); err != nil {
		return "", err
	}
	if len(result.Choices) == 0 {
		return "", errors.New("模型没有返回压缩摘要")
	}
	msg := result.Choices[0].Message
	if msg.Content != nil && strings.TrimSpace(*msg.Content) != "" {
		return strings.TrimSpace(*msg.Content), nil
	}
	// 推理模型（deepseek-reasoner 系）正文在 reasoning_content，content 可能为空
	if msg.ReasoningContent != nil && strings.TrimSpace(*msg.ReasoningContent) != "" {
		return strings.TrimSpace(*msg.ReasoningContent), nil
	}
	return "", errors.New("模型没有返回压缩摘要")
}

// stream 发起流式 LLM 请求，对连接失败、限流(429/5xx/551)、首 chunk 前流断开
// 做指数退避重试（3 次：1s→2s→4s）。保留 (<-chan, error) 签名，上层无需改动。
func (c *LLMClient) stream(ctx context.Context, body map[string]any) (<-chan StreamChunk, error) {
	anthropic := c.isAnthropic()
	url := c.apiConfig.BaseURL()
	if anthropic {
		url = normalizeAnthropicURL(url)
	}
	payload, err := json.Marshal(body)
	if err != nil {
		return nil, err
	}

	const maxRetries = 3
	var lastErr error

	for attempt := 0; attempt <= maxRetries; attempt++ {
		if attempt > 0 {
			backoff := time.Duration(1<<(attempt-1)) * time.Second
			select {
			case <-ctx.Done():
				return nil, ctx.Err()
			case <-time.After(backoff):
			}
		}

		ch, reqErr := c.doStreamRequest(ctx, anthropic, url, payload)
		if reqErr != nil {
			lastErr = reqErr
			if attempt < maxRetries && isRetriableErr(reqErr) {
				log.Printf("[llm] 流式请求失败（第 %d 次），将重试: %v", attempt+1, reqErr)
				continue
			}
			return nil, reqErr
		}

		// 请求成功建立，但流可能在首个 chunk 前就断开（EOF）。
		// peek 第一个 chunk：如果是可重试错误，排空旧 channel 后重试。
		first, ok := <-ch
		if !ok {
			lastErr = errors.New("stream: 空响应")
			if attempt < maxRetries {
				log.Printf("[llm] 流式响应为空（第 %d 次），重试", attempt+1)
				continue
			}
			return nil, lastErr
		}
		if first.Error != nil && attempt < maxRetries && isRetriableErr(first.Error) {
			for range ch {
			}
			lastErr = first.Error
			log.Printf("[llm] 流式响应首 chunk 前断开（第 %d 次），重试: %v", attempt+1, first.Error)
			continue
		}

		// 正常：转发 first + 后续 chunk
		out := make(chan StreamChunk, 64)
		go func() {
			defer close(out)
			out <- first
			for chunk := range ch {
				out <- chunk
			}
		}()
		return out, nil
	}

	return nil, lastErr
}

// doStreamRequest 发起单次流式 LLM 请求（不含重试）。
func (c *LLMClient) doStreamRequest(ctx context.Context, anthropic bool, url string, payload []byte) (<-chan StreamChunk, error) {
	ctx, cancel := context.WithTimeout(ctx, c.cfg.RequestTimeout)

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(payload))
	if err != nil {
		cancel()
		return nil, err
	}
	c.setRequestHeaders(req, anthropic)

	resp, err := c.client.Do(req)
	if err != nil {
		cancel()
		return nil, err
	}
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		apiErr := c.readAPIError(resp)
		resp.Body.Close()
		cancel()
		return nil, apiErr
	}

	ch := make(chan StreamChunk, 64)
	go func() {
		defer cancel()
		defer close(ch)
		defer resp.Body.Close()
		if anthropic {
			readAnthropicSSE(ctx, resp, ch)
		} else {
			c.readSSE(ctx, resp, ch)
		}
	}()
	return ch, nil
}

// doSyncRequestWithRetry 发起同步（非流式）LLM 请求，对连接失败、限流(429/5xx/551)
// 做指数退避重试（3 次：1s→2s→4s）。返回响应体字节，由调用方解析。
func (c *LLMClient) doSyncRequestWithRetry(ctx context.Context, anthropic bool, url string, payload []byte) ([]byte, error) {
	const maxRetries = 3
	var lastErr error

	for attempt := 0; attempt <= maxRetries; attempt++ {
		if attempt > 0 {
			backoff := time.Duration(1<<(attempt-1)) * time.Second
			select {
			case <-ctx.Done():
				return nil, ctx.Err()
			case <-time.After(backoff):
			}
		}

		body, err := c.doSyncRequest(ctx, anthropic, url, payload)
		if err == nil {
			return body, nil
		}
		lastErr = err
		if attempt < maxRetries && isRetriableErr(err) {
			log.Printf("[llm] 同步请求失败（第 %d 次），将重试: %v", attempt+1, err)
			continue
		}
		return nil, err
	}
	return nil, lastErr
}

// doSyncRequest 发起单次同步（非流式）LLM 请求（不含重试）。
func (c *LLMClient) doSyncRequest(ctx context.Context, anthropic bool, url string, payload []byte) ([]byte, error) {
	ctx, cancel := context.WithTimeout(ctx, c.cfg.RequestTimeout)
	defer cancel()

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(payload))
	if err != nil {
		return nil, err
	}
	c.setRequestHeaders(req, anthropic)

	resp, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return nil, c.readAPIError(resp)
	}
	return io.ReadAll(resp.Body)
}

// isRetriableErr 判断错误是否值得重试（网络错误、限流、服务端错误）。
func isRetriableErr(err error) bool {
	if err == nil {
		return false
	}
	if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
		return true
	}
	msg := err.Error()
	if strings.Contains(msg, "EOF") ||
		strings.Contains(msg, "connection reset") ||
		strings.Contains(msg, "broken pipe") ||
		strings.Contains(msg, "timeout") ||
		strings.Contains(msg, "deadline exceeded") ||
		strings.Contains(msg, "connection refused") ||
		strings.Contains(msg, "no such host") {
		return true
	}
	// HTTP 状态码错误：5xx 服务端错误、429 限流、551 网关熔断
	if strings.HasPrefix(msg, "HTTP 5") ||
		strings.HasPrefix(msg, "HTTP 429") ||
		strings.HasPrefix(msg, "HTTP 551") {
		return true
	}
	return false
}

func (c *LLMClient) readSSE(ctx context.Context, resp *http.Response, ch chan<- StreamChunk) {
	scanner := bufio.NewScanner(resp.Body)
	scanner.Buffer(make([]byte, 64*1024), 1024*1024)
	for scanner.Scan() {
		line := scanner.Text()
		if !strings.HasPrefix(line, "data:") {
			continue
		}
		data := strings.TrimSpace(strings.TrimPrefix(line, "data:"))
		if data == "[DONE]" {
			break
		}

		var chunk struct {
			Choices []struct {
				Delta struct {
					Content   *string `json:"content"`
					ToolCalls []struct {
						Index    *int   `json:"index"`
						ID       string `json:"id"`
						Function struct {
							Name      string `json:"name"`
							Arguments string `json:"arguments"`
						} `json:"function"`
					} `json:"tool_calls"`
				} `json:"delta"`
				FinishReason *string `json:"finish_reason"`
			} `json:"choices"`
			Usage *Usage `json:"usage"`
		}
		if err := json.Unmarshal([]byte(data), &chunk); err != nil {
			continue
		}

		event := StreamChunk{}
		for _, choice := range chunk.Choices {
			if choice.Delta.Content != nil {
				event.Content += *choice.Delta.Content
			}
			for _, tc := range choice.Delta.ToolCalls {
				index := 0
				if tc.Index != nil {
					index = *tc.Index
				}
				event.ToolCalls = append(event.ToolCalls, ToolCallDelta{
					Index:          index,
					ID:             tc.ID,
					Name:           tc.Function.Name,
					ArgumentsDelta: tc.Function.Arguments,
				})
			}
			if choice.FinishReason != nil {
				event.FinishReason = *choice.FinishReason
			}
		}
		if chunk.Usage != nil {
			event.Usage = chunk.Usage
		}
		if event.Content != "" || len(event.ToolCalls) > 0 || event.FinishReason != "" || event.Usage != nil {
			ch <- event
		}
	}
	if err := scanner.Err(); err != nil && ctx.Err() == nil {
		ch <- StreamChunk{Error: err}
	}
}

func (c *LLMClient) setRequestHeaders(req *http.Request, anthropic bool) {
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "text/event-stream")
	if anthropic {
		req.Header.Set("x-api-key", c.apiConfig.APIKey())
		req.Header.Set("anthropic-version", "2023-06-01")
	} else {
		req.Header.Set("Authorization", "Bearer "+c.apiConfig.APIKey())
	}
}

func (c *LLMClient) readAPIError(resp *http.Response) error {
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 8*1024))
	var apiErr struct {
		Error struct {
			Message string `json:"message"`
		} `json:"error"`
	}
	_ = json.Unmarshal(body, &apiErr)
	message := strings.TrimSpace(apiErr.Error.Message)
	if message == "" {
		message = strings.TrimSpace(string(body))
	}
	if message == "" {
		message = fmt.Sprintf("HTTP %d", resp.StatusCode)
	}
	return errors.New(message)
}

func toAPIMessages(messages []Message) []map[string]any {
	result := make([]map[string]any, 0, len(messages))
	for _, message := range messages {
		item := map[string]any{
			"role":    message.Role,
			"content": contentValue(message.Content),
		}
		if len(message.ToolCalls) > 0 {
			item["tool_calls"] = toAPIToolCalls(message.ToolCalls)
		}
		if message.ToolCallID != "" {
			item["tool_call_id"] = message.ToolCallID
		}
		result = append(result, item)
	}
	return result
}

func toAPIToolCalls(calls []ToolCall) []map[string]any {
	result := make([]map[string]any, 0, len(calls))
	for _, call := range calls {
		result = append(result, map[string]any{
			"id":   call.ID,
			"type": "function",
			"function": map[string]any{
				"name":      call.Function.Name,
				"arguments": call.Function.Arguments,
			},
		})
	}
	return result
}

func contentValue(content *string) any {
	if content == nil {
		return nil
	}
	return *content
}
