package service

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"time"

	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/config"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/log"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/thirdapi/ai"
	clinictracing "github.com/pingcap-inc/clinic/clinic_data_service/pkg/tracing"
	"go.uber.org/zap"
)

// DefaultHealthCheckPrompt 默认巡检报告分析提示词（简洁摘要风格）
const DefaultHealthCheckPrompt = `你是一位资深 TiDB DBA。请对以下巡检报告 JSON 生成一份简洁的健康摘要，严格控制在 500 字以内。

要求：
1. 用一句话给出整体结论和风险等级（🟢健康 / 🟡需关注 / 🔴高风险）
2. 列出 Top 3 关键风险点，每个风险用一行描述：问题 + 影响 + 建议动作
3. 关键指标速览：仅列出异常或需关注的指标，正常指标不要列出
4. 最多给出 3 条优先行动项，按 P0 > P1 > P2 排列

数据说明：
- 内存单位：avg_max_mem_kb / max_max_mem_kb 的单位是 KB
- 时间单位：avg_query_time_s / max_query_time_s 的单位是秒
- threshold 是当前值与14天基线的偏差百分比
- status 为 "No Risk" 表示无风险，"Need Attention" 表示需关注

输出格式：纯文本，不要使用表格，不要展开细节分析。像写给领导的汇报邮件一样简洁。`

// AIAnalyzeRequest AI 分析请求
type AIAnalyzeRequest struct {
	OrgID     string   `json:"orgId" binding:"required"`
	ClusterID string   `json:"clusterId" binding:"required"`
	ReportID  []string `json:"reportId" binding:"required"` // 改为数组，支持多报告
	Model     string   `json:"model" binding:"required"`    // "openai" 或 "claude"
	Prompt    string   `json:"prompt"`                      // 可选，有默认值
}

// AIAnalyzeResponse AI 分析响应
type AIAnalyzeResponse struct {
	OrgID        string   `json:"orgId"`
	ClusterID    string   `json:"clusterId"`
	ReportIDs    []string `json:"reportIds"` // 改为数组
	Model        string   `json:"model"`
	Analysis     string   `json:"analysis"`
	InputTokens  int      `json:"inputTokens"`
	OutputTokens int      `json:"outputTokens"`
}

// AIService AI 分析服务
type AIService struct {
	clients map[string]ai.AIClient
	config  *config.DataProxyConfig
}

// NewAIService 创建 AI 分析服务
func NewAIService(cfg *config.DataProxyConfig) (*AIService, error) {
	clients := make(map[string]ai.AIClient)

	if cfg.AI.OpenAI.APIKey != "" {
		clients["openai"] = ai.NewOpenAIClient(
			cfg.AI.OpenAI.APIKey,
			cfg.AI.OpenAI.BaseURL,
			cfg.AI.OpenAI.Model,
		)
	}

	if cfg.AI.Bedrock.AccessKeyID != "" && cfg.AI.Bedrock.SecretAccessKey != "" {
		bedrockClient, err := ai.NewBedrockClaudeClient(
			cfg.AI.Bedrock.Region,
			cfg.AI.Bedrock.AccessKeyID,
			cfg.AI.Bedrock.SecretAccessKey,
			cfg.AI.Bedrock.Model,
		)
		if err != nil {
			return nil, fmt.Errorf("init Bedrock client: %w", err)
		}
		clients["claude"] = bedrockClient
	}

	if len(clients) == 0 {
		return nil, fmt.Errorf("no AI client configured, need at least one of openai or bedrock")
	}

	return &AIService{clients: clients, config: cfg}, nil
}

// Analyze 执行 AI 分析
func (s *AIService) Analyze(ctx context.Context, req *AIAnalyzeRequest) (*AIAnalyzeResponse, error) {
	start := time.Now()
	log.Info(ctx, "AI analysis started",
		zap.String("orgId", req.OrgID),
		zap.String("clusterId", req.ClusterID),
		zap.String("model", req.Model),
		zap.Int("reportCount", len(req.ReportID)),
	)

	// 1. 批量获取多个巡检报告
	fetchStart := time.Now()
	files, err := s.fetchReportsAsFiles(ctx, req.OrgID, req.ClusterID, req.ReportID)
	if err != nil {
		log.Error(ctx, "Fetch reports failed",
			zap.String("orgId", req.OrgID),
			zap.String("clusterId", req.ClusterID),
			zap.String("model", req.Model),
			zap.Int("reportCount", len(req.ReportID)),
			zap.Duration("latency", time.Since(fetchStart)),
			zap.Error(err),
		)
		return nil, fmt.Errorf("fetch reports failed: %w", err)
	}

	log.Info(ctx, "Fetched reports for analysis",
		zap.Int("totalReports", len(files)),
		zap.Strings("reportIds", req.ReportID),
		zap.Duration("latency", time.Since(fetchStart)),
	)

	// 2. 获取 AI 客户端
	client, ok := s.clients[req.Model]
	if !ok {
		available := make([]string, 0, len(s.clients))
		for k := range s.clients {
			available = append(available, k)
		}
		log.Error(ctx, "Unsupported AI model",
			zap.String("model", req.Model),
			zap.Strings("available", available),
		)
		return nil, fmt.Errorf("unsupported model: %s, available: %v", req.Model, available)
	}

	// 3. 构造 Prompt
	prompt := req.Prompt
	if prompt == "" {
		prompt = DefaultHealthCheckPrompt
	}

	// 如果有多个报告，在 prompt 中说明
	// if len(files) > 1 {
	// 	prompt += fmt.Sprintf("\n\n注意：本次分析包含 %d 个巡检报告，请综合分析所有报告。", len(files))
	// }
	log.Info(ctx, "AI analysis prompt prepared",
		zap.Int("promptLength", len(prompt)),
		zap.Bool("useDefaultPrompt", req.Prompt == ""),
		zap.Int("fileCount", len(files)),
	)

	// 4. 构造请求
	chatReq := &ai.ChatRequest{
		SystemPrompt: prompt,
		Files:        files, // 直接使用文件方式（只支持 OpenAI）
		MaxTokens:    8192,
		Temperature:  0.3,
	}

	// 5. 调用 AI
	totalSize := 0
	for _, file := range files {
		totalSize += len(file.Content)
	}

	log.Info(ctx, "Calling AI model",
		zap.String("model", req.Model),
		zap.Int("fileCount", len(files)),
		zap.Int("totalSize", totalSize),
		zap.Bool("useFiles", len(chatReq.Files) > 0),
	)

	chatStart := time.Now()
	chatResp, err := client.Chat(ctx, chatReq)
	if err != nil {
		log.Error(ctx, "AI model call failed",
			zap.String("model", req.Model),
			zap.Int("fileCount", len(files)),
			zap.Int("totalSize", totalSize),
			zap.Duration("latency", time.Since(chatStart)),
			zap.Error(err),
		)
		return nil, fmt.Errorf("AI analysis failed: %w", err)
	}

	log.Info(ctx, "AI analysis completed",
		zap.String("model", chatResp.Model),
		zap.Int("inputTokens", chatResp.InputTokens),
		zap.Int("outputTokens", chatResp.OutputTokens),
		zap.Duration("latency", time.Since(chatStart)),
		zap.Duration("totalLatency", time.Since(start)),
	)

	return &AIAnalyzeResponse{
		OrgID:        req.OrgID,
		ClusterID:    req.ClusterID,
		ReportIDs:    req.ReportID,
		Model:        chatResp.Model,
		Analysis:     chatResp.Content,
		InputTokens:  chatResp.InputTokens,
		OutputTokens: chatResp.OutputTokens,
	}, nil
}

// fetchReportsAsFiles 批量获取多个巡检报告，返回 FileInput 数组
// 严格保证顺序：任何一个报告获取失败，整个请求立即失败
func (s *AIService) fetchReportsAsFiles(ctx context.Context, orgID, clusterID string, reportIDs []string) ([]ai.FileInput, error) {
	start := time.Now()
	log.Info(ctx, "Fetching healthcheck reports",
		zap.String("orgId", orgID),
		zap.String("clusterId", clusterID),
		zap.Int("reportCount", len(reportIDs)),
		zap.Strings("reportIds", reportIDs),
	)

	files := make([]ai.FileInput, 0, len(reportIDs))

	for i, reportID := range reportIDs {
		// 获取单个报告
		reportData, err := s.fetchSingleReport(ctx, orgID, clusterID, reportID)
		if err != nil {
			// 立即返回错误，不跳过，保证顺序
			return nil, fmt.Errorf("failed to fetch report[%d] %s: %w", i, reportID, err)
		}

		// 构造 FileInput
		files = append(files, ai.FileInput{
			Content:  reportData,
			Name:     fmt.Sprintf("healthcheck_%s_%s.json", clusterID, reportID),
			MimeType: "application/json",
		})

		log.Info(ctx, "Fetched healthcheck report",
			zap.Int("index", i),
			zap.String("reportId", reportID),
			zap.Int("size", len(reportData)),
		)
	}

	log.Info(ctx, "Fetched healthcheck reports completed",
		zap.String("orgId", orgID),
		zap.String("clusterId", clusterID),
		zap.Int("requested", len(reportIDs)),
		zap.Int("success", len(files)),
		zap.Duration("latency", time.Since(start)),
	)
	return files, nil
}

// fetchSingleReport 获取单个巡检报告字节数组
func (s *AIService) fetchSingleReport(ctx context.Context, orgID, clusterID, reportID string) ([]byte, error) {
	// if config.IsLocal() {
	// 	// 本地模式：读取固定文件（测试用）
	// 	log.Info(ctx, "Reading local healthcheck report file")
	// 	data, err := os.ReadFile("output/healthcheck-report/healthcheck_resout.json")
	// 	if err != nil {
	// 		return nil, fmt.Errorf("read local report failed: %w", err)
	// 	}
	// 	return data, nil
	// }

	// 远程模式：调用 Clinic API
	url := fmt.Sprintf("%s/compute-engine/%s/%s/healthcheck/result/%s",
		s.config.ClinicAPIPublic, orgID, clusterID, reportID)

	start := time.Now()
	log.Info(ctx, "Fetching healthcheck report from clinic API",
		zap.String("url", url),
		zap.String("reportId", reportID),
	)

	httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, fmt.Errorf("create request: %w", err)
	}
	httpReq.Header.Set("Authorization", "Bearer "+s.config.AI.ClinicApiKey)

	client := &http.Client{
		Timeout:   30 * time.Second,
		Transport: clinictracing.NewHTTPTransport(nil),
	}
	resp, err := client.Do(httpReq)
	if err != nil {
		log.Error(ctx, "Send request to clinic API failed",
			zap.String("reportId", reportID),
			zap.Duration("latency", time.Since(start)),
			zap.Error(err),
		)
		return nil, fmt.Errorf("send request: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Error(ctx, "Read clinic API response failed",
			zap.String("reportId", reportID),
			zap.Int("statusCode", resp.StatusCode),
			zap.Duration("latency", time.Since(start)),
			zap.Error(err),
		)
		return nil, fmt.Errorf("read response: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		log.Warn(ctx, "Clinic API returned non-200 response",
			zap.String("reportId", reportID),
			zap.Int("statusCode", resp.StatusCode),
			zap.Int("responseSize", len(body)),
			zap.Duration("latency", time.Since(start)),
		)
		return nil, fmt.Errorf("clinic API error (status %d): %s", resp.StatusCode, string(body))
	}

	log.Info(ctx, "Fetched healthcheck report from clinic API completed",
		zap.String("reportId", reportID),
		zap.Int("statusCode", resp.StatusCode),
		zap.Int("size", len(body)),
		zap.Duration("latency", time.Since(start)),
	)
	return body, nil
}
