package services

import (
	"encoding/json"
	"os"
	"regexp"
	"strings"
)

// A server-side, explicit opt-in for new simulator runs. Real AITurn metrics
// never need conversation text and do not inherit this debug setting.
func SimulationLogContentEnabled() bool {
	return strings.EqualFold(strings.TrimSpace(os.Getenv("AI_SIMULATION_LOG_CONTENT")), "true")
}

var diagnosticBearer = regexp.MustCompile(`(?i)\bBearer\s+[A-Za-z0-9._~+/=-]+`)
var diagnosticAPIKey = regexp.MustCompile(`\bsk-[A-Za-z0-9_-]{8,}`)
var diagnosticCredential = regexp.MustCompile(`(?i)\b(api[_-]?key|access[_-]?token|refresh[_-]?token|password|secret|token|authorization)\s*[:=]\s*["']?[^\s&"'<>;,}]+`)

// Best-effort masking of recognizable credentials, including operator notes.
// This is not a general PII detector: debug content still requires careful use.
func RedactDiagnosticText(text string) string {
	text = diagnosticBearer.ReplaceAllString(text, "Bearer [redacted]")
	text = diagnosticAPIKey.ReplaceAllString(text, "[redacted]")
	return diagnosticCredential.ReplaceAllString(text, "$1=[redacted]")
}

func diagnosticSensitiveKey(key string) bool {
	key = strings.NewReplacer("_", "", "-", "", " ", "").Replace(strings.ToLower(key))
	return strings.Contains(key, "secret") || strings.Contains(key, "password") || strings.Contains(key, "apikey") || strings.Contains(key, "authorization") || strings.Contains(key, "reasoning") || key == "systemprompt" || key == "token" || key == "accesstoken" || key == "refreshtoken" || key == "sessiontoken" || key == "authtoken"
}

// Use an allowlist at the persistence boundary so newly added payload fields
// remain private by default. Source excerpts and raw errors are never metadata.
func simulationEventData(data map[string]any, includeContent bool, stage string) map[string]any {
	if includeContent {
		return simulationSafeData(data)
	}
	out := map[string]any{}
	for _, key := range []string{
		"turn_id", "session_id", "content_mode", "user_id", "question_chars", "history_message_count",
		"provider", "model", "response_mode", "response_preference_source", "preference_source", "response_max_chars", "max_chars", "response_chars", "response_validated", "response_retried", "response_kind", "response_policy",
		"issue", "error_code", "answer_check_issue", "handoff_reason", "duration_ms", "latency_ms", "input_tokens", "output_tokens", "input_bytes", "output_bytes",
		"tool_names", "tool_calls", "selected_tools", "knowledge_ids", "product_ids", "retrieval_mode", "remaining_data_calls", "model_calls", "model_call", "model_call_limit", "shipping_tool_used", "rating", "truncated",
	} {
		if value, ok := data[key]; ok {
			out[key] = value
		}
	}
	if settings, ok := data["settings"].(map[string]any); ok {
		clean := map[string]any{}
		for _, key := range []string{"agentic_enabled", "ai_enabled", "response_length", "tone", "persona_fingerprint"} {
			if value, ok := settings[key]; ok {
				clean[key] = value
			}
		}
		out["settings"] = clean
	}
	// Feedback is an explicit write by the operator, not automatically captured
	// conversation content. Preserve it for evaluation, with credential masking.
	if stage == "feedback" {
		if note, ok := data["note"].(string); ok {
			out["note"] = note
		}
	}
	return simulationSafeData(out)
}

func diagnosticJSONText(text string, depth int) string {
	var nested any
	if json.Unmarshal([]byte(text), &nested) == nil {
		switch nested.(type) {
		case map[string]any, []any:
			if raw, err := json.Marshal(simulationSafeValue(nested, depth+1)); err == nil {
				return string(raw)
			}
		}
	}
	return RedactDiagnosticText(text)
}
