package provider

import (
	"bufio"
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"time"

	ai "GoNavi-Wails/internal/ai"
	"GoNavi-Wails/internal/logger"
)

var claudeLookPath = exec.LookPath
var claudeCommandContext = exec.CommandContext
var claudeCLIRequestTimeout = 90 * time.Second

// ClaudeCLIProvider 通过 Claude Code CLI 发送聊天请求
// 适用于 anyrouter/newapi 等只支持 Claude Code 协议的代理服务
type ClaudeCLIProvider struct {
	config ai.ProviderConfig
}

// NewClaudeCLIProvider 创建 ClaudeCLIProvider 实例
func NewClaudeCLIProvider(config ai.ProviderConfig) (Provider, error) {
	return &ClaudeCLIProvider{config: config}, nil
}

func (p *ClaudeCLIProvider) Name() string {
	return "ClaudeCLI"
}

func (p *ClaudeCLIProvider) Validate() error {
	_, err := claudeLookPath("claude")
	if err != nil {
		return fmt.Errorf("未找到 claude 命令，请先安装 Claude Code CLI: npm install -g @anthropic-ai/claude-code")
	}
	if _, err := resolveClaudeCodeGitBashPath(os.Environ(), runtime.GOOS, claudeLookPath, fileExists); err != nil {
		return err
	}
	return nil
}

// Chat 非流式聊天：调用 claude -p "prompt" --output-format json
func (p *ClaudeCLIProvider) Chat(ctx context.Context, req ai.ChatRequest) (*ai.ChatResponse, error) {
	if err := p.Validate(); err != nil {
		return nil, err
	}

	ctx, cancel := ensureClaudeCLITimeout(ctx, claudeCLIRequestTimeout)
	defer cancel()

	prompt := buildPrompt(req.Messages)
	args := []string{"-p", prompt, "--output-format", "json", "--no-session-persistence"}
	if p.config.Model != "" {
		args = append(args, "--model", p.config.Model)
	}

	cmd := claudeCommandContext(ctx, "claude", args...)
	if err := p.setEnv(cmd); err != nil {
		return nil, err
	}

	requestLog := logAIUpstreamRequestStart(
		p.Name(),
		"CLI",
		claudeCLIEndpointForLog(p.config),
		buildClaudeCLIRequestLogBody("json", args, prompt, p.config, req),
	)
	var requestErr error
	defer func() {
		logAIUpstreamRequestFinish(requestLog, 0, requestErr)
	}()

	output, err := cmd.Output()
	if err != nil {
		if isClaudeCLITimeout(ctx, err) {
			requestErr = fmt.Errorf("claude CLI 执行超时（%s），当前 Base URL 或 API Key 可能没有返回有效响应", claudeCLIRequestTimeout)
			return nil, requestErr
		}
		if exitErr, ok := err.(*exec.ExitError); ok {
			requestErr = fmt.Errorf("claude CLI 执行失败: %s", string(exitErr.Stderr))
			return nil, requestErr
		}
		requestErr = fmt.Errorf("claude CLI 执行失败: %w", err)
		return nil, requestErr
	}

	// 解析 JSON 输出
	var result cliStreamEvent
	if err := json.Unmarshal(output, &result); err != nil {
		// 如果 JSON 解析失败，直接返回原始文本
		return &ai.ChatResponse{Content: strings.TrimSpace(string(output))}, nil
	}
	if errMsg, hasError := extractClaudeCLIEventError(result); hasError {
		requestErr = fmt.Errorf("claude CLI 返回错误: %s", errMsg)
		return nil, requestErr
	}

	return &ai.ChatResponse{Content: result.Result}, nil
}

// ChatStream 流式聊天：调用 claude -p "prompt" --output-format stream-json
func (p *ClaudeCLIProvider) ChatStream(ctx context.Context, req ai.ChatRequest, callback func(ai.StreamChunk)) error {
	if err := p.Validate(); err != nil {
		return err
	}

	ctx, cancel := ensureClaudeCLITimeout(ctx, claudeCLIRequestTimeout)
	defer cancel()

	prompt := buildPrompt(req.Messages)
	args := []string{"-p", prompt, "--output-format", "stream-json", "--verbose", "--include-partial-messages", "--no-session-persistence"}
	if p.config.Model != "" {
		args = append(args, "--model", p.config.Model)
	}

	cmd := claudeCommandContext(ctx, "claude", args...)
	if err := p.setEnv(cmd); err != nil {
		return err
	}

	requestLog := logAIUpstreamRequestStart(
		p.Name(),
		"CLI",
		claudeCLIEndpointForLog(p.config),
		buildClaudeCLIRequestLogBody("stream-json", args, prompt, p.config, req),
	)
	var requestErr error
	defer func() {
		logAIUpstreamRequestFinish(requestLog, 0, requestErr)
	}()

	// 关闭 stdin，防止 claude CLI 等待输入
	cmd.Stdin = nil

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		requestErr = fmt.Errorf("创建 stdout 管道失败: %w", err)
		return requestErr
	}

	// 捕获 stderr
	var stderrBuf bytes.Buffer
	cmd.Stderr = &stderrBuf

	if err := cmd.Start(); err != nil {
		requestErr = fmt.Errorf("启动 claude CLI 失败: %w", err)
		return requestErr
	}

	if cmd.Process != nil {
		logger.Infof("ClaudeCLI 请求进程已启动：requestId=%s pid=%d", requestLog.id, cmd.Process.Pid)
	}

	// 前端已有 loading 动画，无需在 content 中注入"正在思考"

	// 逐行读取流式 JSON 输出
	scanner := bufio.NewScanner(stdout)
	scanner.Buffer(make([]byte, 64*1024), 1024*1024)

	for scanner.Scan() {
		line := scanner.Text()
		if strings.TrimSpace(line) == "" {
			continue
		}

		var event cliStreamEvent
		if err := json.Unmarshal([]byte(line), &event); err != nil {
			logger.Warnf("ClaudeCLI 忽略非 JSON 输出：requestId=%s line=%s", requestLog.id, RedactAIUpstreamLogText(line))
			continue
		}

		switch event.Type {
		case "system":
			if isClaudeCLISystemRetryEvent(event) {
				if errMsg, hasError := extractClaudeCLISystemRetryError(event); hasError {
					callback(ai.StreamChunk{Error: errMsg, Done: true})
					requestErr = fmt.Errorf("claude CLI 鉴权失败: %s", errMsg)
					if cmd.Process != nil {
						_ = cmd.Process.Kill()
					}
					_ = cmd.Wait()
					return nil
				}
			}
		case "assistant":
			if errMsg, hasError := extractClaudeCLIEventError(event); hasError {
				callback(ai.StreamChunk{Error: errMsg, Done: true})
				requestErr = fmt.Errorf("claude CLI 返回错误: %s", errMsg)
				_ = cmd.Wait()
				return nil
			}
			// 助手消息开始或文本内容
			if event.Message.Content != nil {
				for _, block := range event.Message.Content {
					if block.Type == "thinking" && block.Thinking != "" {
						callback(ai.StreamChunk{Thinking: block.Thinking})
					} else if block.Type == "text" && block.Text != "" {
						callback(ai.StreamChunk{Content: block.Text})
					}
				}
			}
		case "content_block_delta":
			// 增量文本或增量思考
			if event.Delta.Type == "thinking_delta" && event.Delta.Thinking != "" {
				callback(ai.StreamChunk{Thinking: event.Delta.Thinking})
			} else if event.Delta.Text != "" {
				callback(ai.StreamChunk{Content: event.Delta.Text})
			}
		case "result":
			if errMsg, hasError := extractClaudeCLIEventError(event); hasError {
				callback(ai.StreamChunk{Error: errMsg, Done: true})
				requestErr = fmt.Errorf("claude CLI 返回错误: %s", errMsg)
				_ = cmd.Wait()
				return nil
			}
			// 最终结果事件 — 不发送 content（assistant 事件已包含），只标记完成
			callback(ai.StreamChunk{Done: true})
			_ = cmd.Wait()
			return nil
		case "error":
			errMsg, _ := extractClaudeCLIEventError(event)
			callback(ai.StreamChunk{Error: errMsg, Done: true})
			requestErr = fmt.Errorf("claude CLI 返回错误: %s", errMsg)
			_ = cmd.Wait()
			return nil
		}
	}

	waitErr := cmd.Wait()
	stderrStr := strings.TrimSpace(stderrBuf.String())

	if isClaudeCLITimeout(ctx, waitErr) {
		requestErr = fmt.Errorf("claude CLI 执行超时（%s），当前 Base URL 或 API Key 可能没有返回有效响应", claudeCLIRequestTimeout)
		callback(ai.StreamChunk{
			Error: requestErr.Error(),
			Done:  true,
		})
		return nil
	}

	if waitErr != nil {
		errMsg := fmt.Sprintf("claude CLI 异常退出: %v", waitErr)
		if stderrStr != "" {
			errMsg = fmt.Sprintf("claude CLI 异常退出: %s", stderrStr)
		}
		requestErr = fmt.Errorf("%s", errMsg)
		callback(ai.StreamChunk{Error: errMsg, Done: true})
		return nil
	}

	callback(ai.StreamChunk{Done: true})
	return nil
}

func ensureClaudeCLITimeout(ctx context.Context, timeout time.Duration) (context.Context, context.CancelFunc) {
	if _, hasDeadline := ctx.Deadline(); hasDeadline || timeout <= 0 {
		return ctx, func() {}
	}
	return context.WithTimeout(ctx, timeout)
}

func isClaudeCLITimeout(ctx context.Context, err error) bool {
	if err == nil {
		return false
	}
	return errors.Is(ctx.Err(), context.DeadlineExceeded) || errors.Is(err, context.DeadlineExceeded)
}

func claudeCLIEndpointForLog(config ai.ProviderConfig) string {
	baseURL := strings.TrimRight(strings.TrimSpace(config.BaseURL), "/")
	if baseURL != "" {
		return sanitizeAIUpstreamURL(baseURL)
	}
	return "claude://cli"
}

func buildClaudeCLIRequestLogBody(outputFormat string, args []string, prompt string, config ai.ProviderConfig, req ai.ChatRequest) map[string]any {
	return map[string]any{
		"command":       "claude",
		"args":          claudeCLIArgsForLog(args),
		"prompt":        prompt,
		"output_format": outputFormat,
		"model":         strings.TrimSpace(config.Model),
		"base_url":      claudeCLIEndpointForLog(config),
		"has_api_key":   strings.TrimSpace(config.APIKey) != "",
		"message_count": len(req.Messages),
		"tool_count":    len(req.Tools),
		"tool_names":    claudeCLIToolNamesForLog(req.Tools),
	}
}

func claudeCLIArgsForLog(args []string) []string {
	result := append([]string(nil), args...)
	for i := 0; i < len(result)-1; i++ {
		if result[i] == "-p" {
			result[i+1] = "[prompt logged separately]"
			i++
		}
	}
	return result
}

func claudeCLIToolNamesForLog(tools []ai.Tool) []string {
	names := make([]string, 0, len(tools))
	for _, tool := range tools {
		name := strings.TrimSpace(tool.Function.Name)
		if name != "" {
			names = append(names, name)
		}
	}
	return names
}

// setEnv 设置 Claude CLI 的环境变量
func (p *ClaudeCLIProvider) setEnv(cmd *exec.Cmd) error {
	env, err := buildClaudeCLIEnv(p.config, cmd.Environ(), runtime.GOOS, claudeLookPath, fileExists)
	if err != nil {
		return err
	}
	cmd.Env = env
	return nil
}

func buildClaudeCLIEnv(config ai.ProviderConfig, baseEnv []string, goos string, lookPath func(string) (string, error), exists func(string) bool) ([]string, error) {
	env := append([]string(nil), baseEnv...)
	if config.BaseURL != "" {
		env = upsertEnv(env, "ANTHROPIC_BASE_URL", strings.TrimRight(config.BaseURL, "/"))
	}
	if config.APIKey != "" {
		env = upsertEnv(env, "ANTHROPIC_AUTH_TOKEN", config.APIKey)
		env = upsertEnv(env, "ANTHROPIC_API_KEY", config.APIKey)
	}

	gitBashPath, err := resolveClaudeCodeGitBashPath(env, goos, lookPath, exists)
	if err != nil {
		return nil, err
	}
	if gitBashPath != "" {
		env = upsertEnv(env, "CLAUDE_CODE_GIT_BASH_PATH", gitBashPath)
	}
	return env, nil
}

func resolveClaudeCodeGitBashPath(env []string, goos string, lookPath func(string) (string, error), exists func(string) bool) (string, error) {
	if goos != "windows" {
		return "", nil
	}

	if configured := strings.TrimSpace(envValue(env, "CLAUDE_CODE_GIT_BASH_PATH")); configured != "" {
		if exists(configured) {
			return configured, nil
		}
		return "", fmt.Errorf("Claude Code CLI 在 Windows 下需要 git-bash，但 CLAUDE_CODE_GIT_BASH_PATH 指向的 bash.exe 不存在: %s", configured)
	}

	for _, command := range []string{"bash.exe", "bash"} {
		if bashPath, err := lookPath(command); err == nil && exists(bashPath) {
			return bashPath, nil
		}
	}

	if gitPath, err := lookPath("git.exe"); err == nil {
		gitDir := parentWindowsPath(gitPath)
		for _, candidate := range []string{
			joinWindowsPath(parentWindowsPath(gitDir), "bin", "bash.exe"),
			joinWindowsPath(gitDir, "bash.exe"),
		} {
			if candidate != "" && exists(candidate) {
				return candidate, nil
			}
		}
	}

	for _, candidate := range windowsGitBashCandidates(env) {
		if exists(candidate) {
			return candidate, nil
		}
	}

	return "", fmt.Errorf("Claude Code CLI 在 Windows 下需要 git-bash。请安装 Git for Windows（https://git-scm.com/downloads/win）；如果已安装但未加入 PATH，请设置环境变量 CLAUDE_CODE_GIT_BASH_PATH 指向 bash.exe，例如 C:\\Program Files\\Git\\bin\\bash.exe")
}

func windowsGitBashCandidates(env []string) []string {
	candidates := make([]string, 0, 3)
	for _, base := range []string{
		envValue(env, "ProgramFiles"),
		envValue(env, "ProgramFiles(x86)"),
		envValue(env, "LocalAppData"),
	} {
		base = strings.TrimSpace(base)
		if base == "" {
			continue
		}
		if strings.EqualFold(base, envValue(env, "LocalAppData")) {
			candidates = append(candidates, joinWindowsPath(base, "Programs", "Git", "bin", "bash.exe"))
			continue
		}
		candidates = append(candidates, joinWindowsPath(base, "Git", "bin", "bash.exe"))
	}
	return candidates
}

func envValue(env []string, key string) string {
	prefix := key + "="
	for _, entry := range env {
		if strings.HasPrefix(entry, prefix) {
			return strings.TrimPrefix(entry, prefix)
		}
	}
	return ""
}

func upsertEnv(env []string, key, value string) []string {
	prefix := key + "="
	for i, entry := range env {
		if strings.HasPrefix(entry, prefix) {
			env[i] = prefix + value
			return env
		}
	}
	return append(env, prefix+value)
}

func fileExists(path string) bool {
	info, err := os.Stat(strings.TrimSpace(path))
	return err == nil && !info.IsDir()
}

func joinWindowsPath(base string, parts ...string) string {
	result := strings.TrimSpace(strings.ReplaceAll(base, "/", `\`))
	if result != "" {
		result = strings.TrimRight(result, `\`)
	}

	for _, part := range parts {
		part = strings.Trim(strings.ReplaceAll(strings.TrimSpace(part), "/", `\`), `\`)
		if part == "" {
			continue
		}
		if result == "" {
			result = part
			continue
		}
		result += `\` + part
	}
	return result
}

func parentWindowsPath(path string) string {
	path = strings.TrimRight(strings.ReplaceAll(strings.TrimSpace(path), "/", `\`), `\`)
	idx := strings.LastIndex(path, `\`)
	if idx <= 0 {
		return ""
	}
	return path[:idx]
}

// buildPrompt 将消息列表拼接为适合 claude -p 的提示文本
func buildPrompt(messages []ai.Message) string {
	if len(messages) == 1 {
		return messages[0].Content
	}

	var sb strings.Builder
	for _, m := range messages {
		switch m.Role {
		case "system":
			sb.WriteString("[System]\n")
			sb.WriteString(m.Content)
			sb.WriteString("\n\n")
		case "user":
			sb.WriteString(m.Content)
			sb.WriteString("\n\n")
		case "assistant":
			sb.WriteString("[Previous Assistant Response]\n")
			sb.WriteString(m.Content)
			sb.WriteString("\n\n")
		}
	}
	return strings.TrimSpace(sb.String())
}

// cliStreamEvent Claude CLI stream-json 输出的事件结构
type cliStreamEvent struct {
	Type         string  `json:"type"`
	Subtype      string  `json:"subtype,omitempty"`
	IsError      bool    `json:"is_error,omitempty"`
	Attempt      int     `json:"attempt,omitempty"`
	MaxRetries   int     `json:"max_retries,omitempty"`
	RetryDelayMS float64 `json:"retry_delay_ms,omitempty"`
	ErrorStatus  int     `json:"error_status,omitempty"`
	SessionID    string  `json:"session_id,omitempty"`
	Message      struct {
		Content []struct {
			Type     string `json:"type"`
			Text     string `json:"text"`
			Thinking string `json:"thinking"`
		} `json:"content"`
	} `json:"message,omitempty"`
	Delta struct {
		Type     string `json:"type"`
		Text     string `json:"text"`
		Thinking string `json:"thinking"`
	} `json:"delta,omitempty"`
	Result string              `json:"result,omitempty"`
	Error  cliStreamEventError `json:"error,omitempty"`
}

type cliStreamEventError struct {
	Message string
}

func (e *cliStreamEventError) UnmarshalJSON(data []byte) error {
	trimmed := strings.TrimSpace(string(data))
	if trimmed == "" || trimmed == "null" {
		e.Message = ""
		return nil
	}

	var text string
	if err := json.Unmarshal(data, &text); err == nil {
		e.Message = strings.TrimSpace(text)
		return nil
	}

	var payload struct {
		Message string `json:"message"`
	}
	if err := json.Unmarshal(data, &payload); err != nil {
		return err
	}
	e.Message = strings.TrimSpace(payload.Message)
	return nil
}

func extractClaudeCLIEventError(event cliStreamEvent) (string, bool) {
	if event.Type != "error" && !event.IsError {
		return "", false
	}

	if msg := strings.TrimSpace(event.Result); msg != "" {
		return msg, true
	}

	for _, block := range event.Message.Content {
		if block.Type == "text" && strings.TrimSpace(block.Text) != "" {
			return strings.TrimSpace(block.Text), true
		}
	}

	if msg := strings.TrimSpace(event.Error.Message); msg != "" {
		return msg, true
	}

	return "claude CLI 返回未知错误", true
}

func isClaudeCLISystemRetryEvent(event cliStreamEvent) bool {
	return event.Type == "system" && event.Subtype == "api_retry"
}

func extractClaudeCLISystemRetryError(event cliStreamEvent) (string, bool) {
	if !isClaudeCLISystemRetryEvent(event) {
		return "", false
	}

	errText := strings.TrimSpace(event.Error.Message)
	if event.ErrorStatus != 401 && event.ErrorStatus != 403 && !strings.EqualFold(errText, "authentication_failed") {
		return "", false
	}

	if errText == "" {
		errText = "authentication_failed"
	}

	if event.ErrorStatus > 0 {
		return fmt.Sprintf("claude CLI 鉴权失败 (HTTP %d): %s", event.ErrorStatus, errText), true
	}
	return fmt.Sprintf("claude CLI 鉴权失败: %s", errText), true
}
