package actions

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/go-logr/logr"
	opent "github.com/tidbcloud/devops/entity/operation"
	"github.com/tidbcloud/devops/pkg/infra/clients/cliniccli"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
	"google.golang.org/genai"
)

const (
	PromQLQPS1H       = `sum(rate(tidb_executor_statement_total[1h]))`
	PromQLQPS7D       = `sum(rate(tidb_executor_statement_total[7d]))`
	PromQLConnections = `sum(tidb_server_connections)`
)

// ScaleTarget describes the current and target state of a component being scaled.
type ScaleTarget struct {
	ComponentType    string
	CurrentNodeCount uint32
	CurrentCPUCores  uint32 // 0 = unknown
	CurrentMemoryGi  uint32 // 0 = unknown
	TargetNodeCount  uint32
	TargetCPUCores   uint32 // 0 = unchanged/unknown
	TargetMemoryGi   uint32 // 0 = unchanged/unknown
}

type geminiAdvisoryResp struct {
	RiskLevel string `json:"risk_level"`
	Message   string `json:"message"`
}

// ClusterScaleAdvisory queries Clinic for cluster metrics and asks Gemini to
// assess the risk of the planned scale operation. Returns nil if Clinic or
// Gemini is unavailable.
func ClusterScaleAdvisory(ctx context.Context, logger logr.Logger, clinic cliniccli.Client, geminiCli *genai.Client, clusterIDStr string, target ScaleTarget) ([]opent.Advisory, error) {
	if clinic == nil || geminiCli == nil {
		logger.Info("advisory skipped: client not configured", "clinic_nil", clinic == nil, "gemini_nil", geminiCli == nil)
		return nil, nil
	}

	clusters, err := clinic.ListClusters(ctx, &cliniccli.ClusterFilter{
		ClusterIds: []string{clusterIDStr},
	})
	if err != nil {
		return nil, fmt.Errorf("advisory check failed: clinic unavailable: %w", err)
	}
	if len(clusters) == 0 {
		return nil, fmt.Errorf("advisory check failed: cluster %s not found in clinic", clusterIDStr)
	}
	c := clusters[0]
	now := time.Now()

	queryMetric := func(promql string) float64 {
		resp, err := clinic.QueryClusterMetrics(ctx, cliniccli.QueryClusterMetricsRequest{
			OrgID:     c.OrgID,
			ClusterID: c.ClusterID,
			PromQL:    promql,
			Start:     now,
			End:       now,
			Step:      "1h",
		})
		if err != nil || len(resp.Data.Result) == 0 || len(resp.Data.Result[0].Values) == 0 {
			return -1
		}
		return float64(resp.Data.Result[0].Values[0].Value)
	}

	qps1h := queryMetric(PromQLQPS1H)
	qps7d := queryMetric(PromQLQPS7D)
	connections := queryMetric(PromQLConnections)

	logger.Info("advisory metrics collected",
		"cluster_id", clusterIDStr,
		"target", target,
		"qps_1h", qps1h,
		"qps_7d", qps7d,
		"connections", connections,
	)

	prompt := buildScaleAdvisoryPrompt(target, qps1h, qps7d, connections)
	logger.Info("advisory gemini raw request", "cluster_id", clusterIDStr, "prompt", prompt)

	result, err := geminiCli.Models.GenerateContent(
		ctx,
		"gemini-2.5-flash",
		genai.Text(prompt),
		&genai.GenerateContentConfig{
			Temperature: pointer.Pointer(float32(0.0)),
		},
	)
	if err != nil {
		return nil, fmt.Errorf("AI advisory failed: %w", err)
	}

	text := strings.TrimSpace(result.Text())
	logger.Info("advisory gemini raw response", "cluster_id", clusterIDStr, "raw", text)
	text = strings.TrimPrefix(text, "```json")
	text = strings.TrimPrefix(text, "```")
	text = strings.TrimSuffix(text, "```")
	text = strings.TrimSpace(text)

	var resp geminiAdvisoryResp
	if err := json.Unmarshal([]byte(text), &resp); err != nil || resp.RiskLevel == "none" || resp.Message == "" {
		return nil, nil
	}

	return []opent.Advisory{{
		Level:   "warning",
		Message: fmt.Sprintf("[%s risk] %s", resp.RiskLevel, resp.Message),
	}}, nil
}

func buildScaleAdvisoryPrompt(t ScaleTarget, qps1h, qps7d, connections float64) string {
	var sb strings.Builder

	sb.WriteString("You are a TiDB Cloud database operations advisor.\n\n")
	sb.WriteString(fmt.Sprintf("## Planned Scale Operation: %s\n", t.ComponentType))

	if t.CurrentNodeCount > 0 {
		if t.CurrentCPUCores > 0 {
			sb.WriteString(fmt.Sprintf("- Current: %d nodes × %d vCPU / %d GiB memory per node\n",
				t.CurrentNodeCount, t.CurrentCPUCores, t.CurrentMemoryGi))
		} else {
			sb.WriteString(fmt.Sprintf("- Current: %d nodes\n", t.CurrentNodeCount))
		}
	}
	if t.TargetNodeCount > 0 {
		if t.TargetCPUCores > 0 {
			sb.WriteString(fmt.Sprintf("- Target:  %d nodes × %d vCPU / %d GiB memory per node\n",
				t.TargetNodeCount, t.TargetCPUCores, t.TargetMemoryGi))
		} else {
			sb.WriteString(fmt.Sprintf("- Target:  %d nodes\n", t.TargetNodeCount))
		}
	}

	sb.WriteString("\n## Current Cluster Metrics (Baseline vs Current)\n")
	if qps1h >= 0 {
		sb.WriteString(fmt.Sprintf("- QPS last 1h: %.1f req/s\n", qps1h))
	}
	if qps7d >= 0 {
		sb.WriteString(fmt.Sprintf("- QPS 7-day baseline: %.1f req/s\n", qps7d))
	}
	if connections >= 0 {
		sb.WriteString(fmt.Sprintf("- Active connections: %.0f\n", connections))
	}

	sb.WriteString(`
## Task
Check the current QPS, connections, current node spec of the target component, and whether the planned scale operation poses any risks. Pay special attention to scale-in or spec downgrade — assess whether the remaining capacity can sustain the current load and connection count.

Respond with JSON only (no markdown fences):
{"risk_level": "none|low|medium|high", "message": "<one or two sentence advisory; empty string if risk_level is none>"}
`)
	return sb.String()
}
