package oci

// 真实 OCI GenAI effort 探针(重建版)。
//
// 前身 genai_cache_integration_test.go 承载缓存/亲和/服务端工具等历轮调研模式,
// 调研结论均已归档(.trellis/tasks/archive/2026-07/*/research/),该文件已删除;
// 本文件重建共享基础设施,保留 effort 支持矩阵探测,并新增 responses 端点探测
// (multi-agent 模型仅允许走 Responses 面)。
//
// 运行(默认跳过,不触网):
//
//	OCI_GENAI_CACHE_PROBE=effort-matrix OCI_CACHE_DB=<db 绝对路径> DATA_KEY=<主密钥> \
//	OCI_CACHE_RUN_ID=<唯一标识> go test ./internal/oci/ -run TestRealGenAiEffortMatrix -v
//
// 可选:OCI_CACHE_CONFIG_ID(默认 6)、OCI_CACHE_REGION(默认 us-chicago-1)、
// OCI_EFFORT_CASES 自定义用例("model=effort" 逗号分隔;effort 后缀 "@responses"
// 表示走 /actions/v1/responses 端点,如 "xai.grok-4.20-multi-agent=low@responses")。
//
// 纪律:数据库只读打开;OCI_GO_SDK_DEBUG 必须为空;日志不落原始请求体与凭据,
// 错误消息经 OCID 遮掩后截断。

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"regexp"
	"strings"
	"testing"
	"time"

	"github.com/glebarez/sqlite"
	"github.com/oracle/oci-go-sdk/v65/common"
	"github.com/oracle/oci-go-sdk/v65/generativeaiinference"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"

	projectcrypto "oci-portal/internal/crypto"
	"oci-portal/internal/model"
)

const (
	effortProbeRegion = "us-chicago-1"
	effortProbeLimit  = int64(4 << 20)
)

var effortRunIDPattern = regexp.MustCompile(`^[A-Za-z0-9._-]{1,64}$`)

type effortProbeCase struct {
	model     string
	effort    string
	responses bool // true 走 /actions/v1/responses,否则 typed /actions/chat
	stream    bool // 仅 responses 面:请求 SSE 流,观测事件类型序列
}

type effortProbeResources struct {
	cred      Credentials
	region    string
	modelOCID map[string]string
	inference generativeaiinference.GenerativeAiInferenceClient
}

type effortProbeResult struct {
	status           int
	requestRef       string
	duration         time.Duration
	errorCode        string
	errorMessage     string
	completionTokens int
	reasoningTokens  int
	answerLen        int
	outputTypes      []string
}

// effortMatrixCases 默认批为 grok-4.3 五档基线;全模型矩阵结论见任务归档,
// 复测用 OCI_EFFORT_CASES 自定义。
func effortMatrixCases() []effortProbeCase {
	if raw := os.Getenv("OCI_EFFORT_CASES"); raw != "" {
		return parseEffortCases(raw)
	}
	return []effortProbeCase{
		{model: "xai.grok-4.3", effort: "none", responses: true},
		{model: "xai.grok-4.3", effort: "low", responses: true},
		{model: "xai.grok-4.3", effort: "high", responses: true},
	}
}

// parseEffortCases 解析 "model=effort[@responses|@responses-stream]" 逗号分隔用例。
func parseEffortCases(raw string) []effortProbeCase {
	var out []effortProbeCase
	for _, item := range strings.Split(raw, ",") {
		parts := strings.SplitN(strings.TrimSpace(item), "=", 2)
		if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
			continue
		}
		effort, streaming := strings.CutSuffix(parts[1], "@responses-stream")
		if streaming {
			out = append(out, effortProbeCase{model: parts[0], effort: effort, responses: true, stream: true})
			continue
		}
		effort, _ = strings.CutSuffix(effort, "@responses") // 后缀兼容保留;所有用例均走 responses 面
		out = append(out, effortProbeCase{model: parts[0], effort: effort, responses: true})
	}
	return out
}

func TestRealGenAiEffortMatrix(t *testing.T) {
	requireEffortProbeMode(t, "effort-matrix")
	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Minute)
	defer cancel()
	resources := newEffortProbeResources(t, ctx)
	for _, tc := range effortMatrixCases() {
		runEffortCase(t, ctx, resources, tc)
	}
}

func requireEffortProbeMode(t *testing.T, wanted string) {
	t.Helper()
	if os.Getenv("OCI_GENAI_CACHE_PROBE") != wanted {
		t.Skipf("set OCI_GENAI_CACHE_PROBE=%s to run", wanted)
	}
	if os.Getenv("OCI_GO_SDK_DEBUG") != "" {
		t.Fatal("OCI_GO_SDK_DEBUG must be unset to protect signed requests")
	}
	if !effortRunIDPattern.MatchString(os.Getenv("OCI_CACHE_RUN_ID")) {
		t.Fatal("set a unique OCI_CACHE_RUN_ID (1-64 safe characters)")
	}
}

func runEffortCase(t *testing.T, ctx context.Context, resources effortProbeResources, tc effortProbeCase) {
	t.Helper()
	ocid, ok := resources.modelOCID[tc.model]
	if !ok {
		t.Logf("model=%s effort=%s resolve-failed: model not in on-demand catalog", tc.model, tc.effort)
		return
	}
	_ = ocid
	body, path, err := effortProbeBody(tc)
	if err != nil {
		t.Fatalf("build effort body: %v", err)
	}
	result := executeEffortProbe(ctx, resources, path, body)
	logEffortResult(t, tc, result)
}

// effortProbeBody 构造 OpenAI Responses 直通请求体(typed chat 面已随网关剔除,
// 探针仅保留 responses 端点;历史 typed 结论见任务归档)。
func effortProbeBody(tc effortProbeCase) ([]byte, string, error) {
	question := "How many positive divisors does 360 have? Answer with the number only."
	body := map[string]interface{}{"model": tc.model, "input": question,
		"max_output_tokens": 2048, "store": false}
	if tc.effort != "" {
		body["reasoning"] = map[string]string{"effort": tc.effort}
	}
	if tc.stream {
		body["stream"] = true
	}
	payload, err := json.Marshal(body)
	return payload, "/actions/v1/responses", err
}

// executeEffortProbe 以 IAM 签名裸 POST 指定路径;responses 面按生产实现补 compartment 头。
func executeEffortProbe(ctx context.Context, resources effortProbeResources, path string, body []byte) effortProbeResult {
	client := resources.inference.BaseClient
	client.Configuration.CircuitBreaker = nil
	common.UpdateEndpointTemplateForOptions(&client)
	common.SetMissingTemplateParams(&client)
	request, err := http.NewRequestWithContext(ctx, http.MethodPost, path, strings.NewReader(string(body)))
	if err != nil {
		return effortProbeResult{errorCode: "BuildRequest", errorMessage: err.Error()}
	}
	request.Header.Set("Content-Type", "application/json")
	if path != "/actions/chat" {
		request.Header.Set("CompartmentId", resources.cred.TenancyOCID)
		request.Header.Set("opc-compartment-id", resources.cred.TenancyOCID)
	}
	return effortCallProbe(ctx, client, request)
}

func effortCallProbe(ctx context.Context, client common.BaseClient, request *http.Request) effortProbeResult {
	started := time.Now()
	response, err := client.Call(ctx, request)
	result := effortProbeResult{duration: time.Since(started)}
	if response != nil {
		result.status = response.StatusCode
		result.requestRef = shortEffortRef(response.Header.Get("opc-request-id"))
		defer response.Body.Close()
	}
	if err != nil {
		result.errorCode, result.errorMessage = effortProbeError(err)
		return result
	}
	payload, readErr := io.ReadAll(io.LimitReader(response.Body, effortProbeLimit))
	if readErr != nil {
		result.errorCode, result.errorMessage = "ReadResponse", readErr.Error()
		return result
	}
	parseEffortPayload(&result, payload)
	return result
}

func effortProbeError(err error) (string, string) {
	if serviceErr, ok := common.IsServiceError(err); ok {
		return fmt.Sprintf("%d", serviceErr.GetHTTPStatusCode()), sanitizeEffortText(serviceErr.GetMessage())
	}
	return "CallError", sanitizeEffortText(err.Error())
}

// parseEffortPayload 兼容三种 usage 形态:typed 驼峰(chatResponse.usage.completionTokens)、
// chat 兼容面下划线(usage.completion_tokens)、responses 面(usage.output_tokens)。
func parseEffortPayload(result *effortProbeResult, payload []byte) {
	if text := strings.TrimSpace(string(payload)); strings.HasPrefix(text, "event:") || strings.HasPrefix(text, "data:") {
		parseEffortSSE(result, text)
		return
	}
	var root map[string]interface{}
	if json.Unmarshal(payload, &root) != nil {
		return
	}
	result.answerLen = len(parseEffortAnswer(root))
	result.outputTypes = parseEffortOutputTypes(root)
	if typed, ok := root["chatResponse"].(map[string]interface{}); ok {
		root = typed
	}
	usage, _ := root["usage"].(map[string]interface{})
	for _, key := range []string{"completionTokens", "completion_tokens", "output_tokens"} {
		if v, ok := effortInt(usage, key); ok {
			result.completionTokens = v
			break
		}
	}
	for _, detailsKey := range []string{"completionTokensDetails", "completion_tokens_details", "output_tokens_details"} {
		details, _ := usage[detailsKey].(map[string]interface{})
		for _, key := range []string{"reasoningTokens", "reasoning_tokens"} {
			if v, ok := effortInt(details, key); ok {
				result.reasoningTokens = v
			}
		}
	}
}

// parseEffortAnswer 提取正文文本:typed 取 chatResponse.choices[].message.content[].text,
// responses 取 output[] 里 message 项的 content[].text。仅在内存中量长度,不落日志。
func parseEffortAnswer(root map[string]interface{}) string {
	var sb strings.Builder
	if typed, ok := root["chatResponse"].(map[string]interface{}); ok {
		choices, _ := typed["choices"].([]interface{})
		for _, c := range choices {
			cm, _ := c.(map[string]interface{})
			msg, _ := cm["message"].(map[string]interface{})
			collectEffortText(&sb, msg["content"])
		}
		return sb.String()
	}
	output, _ := root["output"].([]interface{})
	for _, item := range output {
		im, _ := item.(map[string]interface{})
		if im["type"] == "message" {
			collectEffortText(&sb, im["content"])
		}
	}
	return sb.String()
}

func collectEffortText(sb *strings.Builder, content interface{}) {
	parts, _ := content.([]interface{})
	for _, p := range parts {
		pm, _ := p.(map[string]interface{})
		if text, ok := pm["text"].(string); ok {
			sb.WriteString(text)
		}
	}
}

// parseEffortSSE 汇总 SSE 流:事件类型去重序列进 outputTypes,正文增量长度进 answerLen,
// 末尾 completed 事件里的 usage 进 tokens 字段。
func parseEffortSSE(result *effortProbeResult, text string) {
	seen := map[string]bool{}
	for _, line := range strings.Split(text, "\n") {
		if name, ok := strings.CutPrefix(line, "event: "); ok {
			if name = strings.TrimSpace(name); !seen[name] {
				seen[name] = true
				result.outputTypes = append(result.outputTypes, name)
			}
			continue
		}
		data, ok := strings.CutPrefix(line, "data: ")
		if !ok {
			continue
		}
		var ev map[string]interface{}
		if json.Unmarshal([]byte(data), &ev) != nil {
			continue
		}
		if name, ok := ev["type"].(string); ok && !seen[name] { // 纯 data: 行流(无 event: 行)从事件体取类型
			seen[name] = true
			result.outputTypes = append(result.outputTypes, name)
		}
		if delta, ok := ev["delta"].(string); ok && strings.Contains(fmt.Sprint(ev["type"]), "output_text") {
			result.answerLen += len(delta)
		}
		if resp, ok := ev["response"].(map[string]interface{}); ok {
			usage, _ := resp["usage"].(map[string]interface{})
			if v, ok := effortInt(usage, "output_tokens"); ok {
				result.completionTokens = v
			}
		}
	}
}

// parseEffortOutputTypes 收集 responses 面 output 项类型序列(multi-agent 行为观测)。
func parseEffortOutputTypes(root map[string]interface{}) []string {
	output, _ := root["output"].([]interface{})
	var types []string
	for _, item := range output {
		im, _ := item.(map[string]interface{})
		if s, ok := im["type"].(string); ok {
			types = append(types, s)
		}
	}
	return types
}

func effortInt(root map[string]interface{}, key string) (int, bool) {
	if root == nil {
		return 0, false
	}
	if value, ok := root[key].(float64); ok {
		return int(value), true
	}
	return 0, false
}

func logEffortResult(t *testing.T, tc effortProbeCase, result effortProbeResult) {
	t.Helper()
	endpoint := "typed-chat"
	if tc.responses {
		endpoint = "compat-responses"
	}
	t.Logf("endpoint=%s model=%s effort=%s status=%d completion=%d reasoning=%d answer_len=%d output_types=%v request=%s duration_ms=%d error=%s message=%q",
		endpoint, tc.model, tc.effort, result.status, result.completionTokens, result.reasoningTokens,
		result.answerLen, result.outputTypes, result.requestRef, result.duration.Milliseconds(),
		result.errorCode, result.errorMessage)
}

// sanitizeEffortText 遮掩 OCID 并截断,避免错误消息携带租户可定位信息。
func sanitizeEffortText(text string) string {
	text = regexp.MustCompile(`ocid1\.[a-z0-9._-]+`).ReplaceAllString(text, "ocid1.***")
	if len(text) > 300 {
		text = text[:300] + "..."
	}
	return text
}

func shortEffortRef(ref string) string {
	if len(ref) > 12 {
		return ref[:12]
	}
	return ref
}

func effortEnvOr(key, fallback string) string {
	if value := os.Getenv(key); value != "" {
		return value
	}
	return fallback
}

// newEffortProbeResources 装配凭据、区域、inference 客户端与按需模型目录。
func newEffortProbeResources(t *testing.T, ctx context.Context) effortProbeResources {
	t.Helper()
	cred := effortProbeCredentials(t)
	region := effortEnvOr("OCI_CACHE_REGION", effortProbeRegion)
	cred.Region = region
	real := &RealClient{}
	ic, err := real.genAiInferenceClient(cred, region)
	if err != nil {
		t.Fatalf("new inference client: %v", err)
	}
	models, err := real.ListGenAiModels(ctx, cred, region)
	if err != nil {
		t.Fatalf("list models: %s", sanitizeEffortText(err.Error()))
	}
	index := make(map[string]string, len(models))
	for _, m := range models {
		index[m.Name] = m.Ocid
	}
	return effortProbeResources{cred: cred, region: region, modelOCID: index, inference: ic}
}

// effortProbeCredentials 优先从面板数据库(只读)取渠道凭据,否则退回本地 ini 测试凭据。
func effortProbeCredentials(t *testing.T) Credentials {
	t.Helper()
	if dbPath := os.Getenv("OCI_CACHE_DB"); dbPath != "" {
		return effortDBCredentials(t, dbPath)
	}
	return loadTestCredentials(t, effortEnvOr("OCI_TEST_KEY", "试用期"))
}

func effortDBCredentials(t *testing.T, dbPath string) Credentials {
	t.Helper()
	dsn := fmt.Sprintf("file:%s?mode=ro", dbPath)
	db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
	if err != nil {
		t.Fatalf("open read-only probe database: %v", err)
	}
	cipher, err := projectcrypto.NewCipher(os.Getenv("DATA_KEY"))
	if err != nil {
		t.Fatalf("create data cipher: %v", err)
	}
	var config model.OciConfig
	if err := db.First(&config, effortEnvOr("OCI_CACHE_CONFIG_ID", "6")).Error; err != nil {
		t.Fatalf("load OCI config: %v", err)
	}
	return decryptEffortCredentials(t, db, cipher, config)
}

func decryptEffortCredentials(t *testing.T, db *gorm.DB, cipher *projectcrypto.Cipher, config model.OciConfig) Credentials {
	t.Helper()
	privateKey, err := cipher.DecryptString(config.PrivateKeyEnc)
	if err != nil {
		t.Fatalf("decrypt OCI config %d private key: %v", config.ID, err)
	}
	passphrase := ""
	if config.PassphraseEnc != "" {
		if passphrase, err = cipher.DecryptString(config.PassphraseEnc); err != nil {
			t.Fatalf("decrypt OCI config %d passphrase: %v", config.ID, err)
		}
	}
	return Credentials{TenancyOCID: config.TenancyOCID, UserOCID: config.UserOCID,
		Fingerprint: config.Fingerprint, Region: config.Region, PrivateKey: privateKey,
		Passphrase: passphrase, Proxy: loadEffortProxy(t, db, cipher, config.ProxyID)}
}

func loadEffortProxy(t *testing.T, db *gorm.DB, cipher *projectcrypto.Cipher, proxyID *uint) *ProxySpec {
	t.Helper()
	if proxyID == nil {
		return nil
	}
	var proxy model.Proxy
	if err := db.First(&proxy, *proxyID).Error; err != nil {
		t.Fatalf("load proxy %d: %v", *proxyID, err)
	}
	password := ""
	if proxy.PasswordEnc != "" {
		decrypted, err := cipher.DecryptString(proxy.PasswordEnc)
		if err != nil {
			t.Fatalf("decrypt proxy %d password: %v", *proxyID, err)
		}
		password = decrypted
	}
	return &ProxySpec{Type: proxy.Type, Host: proxy.Host, Port: proxy.Port,
		Username: proxy.Username, Password: password}
}

// TestParseEffortCases 断言自定义用例解析:端点后缀、空项与畸形项跳过。
func TestParseEffortCases(t *testing.T) {
	got := parseEffortCases("a=low, b=high@responses ,bad,=x,c=")
	want := []effortProbeCase{{model: "a", effort: "low", responses: true}, {model: "b", effort: "high", responses: true}}
	if len(got) != len(want) {
		t.Fatalf("parseEffortCases = %+v, want %+v", got, want)
	}
	for i := range want {
		if got[i] != want[i] {
			t.Fatalf("case %d = %+v, want %+v", i, got[i], want[i])
		}
	}
}

// TestParseEffortPayload 断言三种 usage 形态与 output 类型序列的解析。
func TestParseEffortPayload(t *testing.T) {
	cases := []struct {
		name       string
		payload    string
		completion int
		reasoning  int
		types      int
	}{
		{"typed 驼峰", `{"chatResponse":{"choices":[{"message":{"content":[{"type":"TEXT","text":"24"}]}}],"usage":{"completionTokens":5,"completionTokensDetails":{"reasoningTokens":9}}}}`, 5, 9, 0},
		{"responses 面", `{"output":[{"type":"reasoning"},{"type":"message","content":[{"type":"output_text","text":"24"}]}],"usage":{"output_tokens":7,"output_tokens_details":{"reasoning_tokens":3}}}`, 7, 3, 2},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			var result effortProbeResult
			parseEffortPayload(&result, []byte(tc.payload))
			if result.completionTokens != tc.completion || result.reasoningTokens != tc.reasoning || len(result.outputTypes) != tc.types {
				t.Fatalf("parse = %+v, want completion=%d reasoning=%d types=%d", result, tc.completion, tc.reasoning, tc.types)
			}
			if result.answerLen != 2 {
				t.Fatalf("answerLen = %d, want 2", result.answerLen)
			}
		})
	}
}
