package openai

import (
	"testing"

	"go.uber.org/zap"
	"go.uber.org/zap/zaptest/observer"
)

func TestLogNoPreviewFields(t *testing.T) {
	// Build a zap logger that captures all emitted fields.
	core, observed := observer.New(zap.DebugLevel)
	logger := zap.New(core)

	// ---- input log (mimics chat_handler.go input path) ----
	logger.Info("openai chat completion input",
		zap.String("model", "test-model"),
		zap.String("target", "test-target"),
		zap.String("adapter", "ollama"),
		zap.Bool("strict_output", false),
		zap.Bool("stream", false),
		zap.Int("message_count", 2),
		zap.Int("prompt_len", 500),
	)

	// ---- output log (mimics chat_handler.go output path) ----
	logger.Info("openai chat completion output",
		zap.String("run_id", "run-123"),
		zap.Bool("strict_output", false),
		zap.Bool("normalized", true),
		zap.Int("content_len", 256),
		zap.Int("reasoning_len", 0),
		zap.String("finish_reason", "stop"),
		zap.Int("tool_call_count", 0),
	)

	// ---- stream closed log (mimics stream.go deferred close) ----
	logger.Info("openai chat completion stream closed",
		zap.String("run_id", "run-456"),
		zap.Bool("strict_output", true),
		zap.Bool("strict_stream_buffer", false),
		zap.Int("content_len", 100),
		zap.Int("reasoning_len", 50),
	)

	// ---- responses input log (mimics responses_handler.go input path) ----
	logger.Info("openai responses input",
		zap.String("model", "resp-model"),
		zap.String("target", "resp-target"),
		zap.String("adapter", "openai_compat"),
		zap.Bool("strict_output", false),
		zap.String("xml_completion_tool", ""),
		zap.Bool("contract_instruction", false),
		zap.Int("prompt_len", 300),
	)

	// ---- responses output log (mimics responses_handler.go output path) ----
	logger.Info("openai responses output",
		zap.String("run_id", "run-789"),
		zap.Bool("strict_output", false),
		zap.String("xml_completion_tool", ""),
		zap.Bool("normalized", true),
		zap.Int("content_len", 200),
		zap.Int("reasoning_len", 0),
	)

	// ---- stream output chunk (mimics logOpenAICompatStreamOutput) ----
	logger.Info("openai chat completion stream output chunk",
		zap.String("run_id", "run-456"),
		zap.Int("seq", 1),
		zap.String("type", "content"),
		zap.Int("delta_len", 32),
		zap.Bool("delta_has_open_think", false),
		zap.Bool("delta_has_close_think", false),
	)

	// ---- suppressed output log ----
	logger.Info("openai chat completion stream output suppressed",
		zap.String("run_id", "run-456"),
		zap.String("type", "content"),
		zap.Int("source_len", 16),
		zap.Bool("source_has_open_think", false),
		zap.Bool("source_has_close_think", false),
	)

	// ---- stream done log ----
	logger.Info("openai chat completion stream output done",
		zap.String("run_id", "run-456"),
		zap.Int("seq", 4),
		zap.String("finish_reason", "stop"),
		zap.Int("content_len", 100),
		zap.Int("reasoning_len", 50),
	)

	// ---- tool_calls chunk log ----
	logger.Info("openai chat completion stream output chunk",
		zap.String("run_id", "run-456"),
		zap.Int("seq", 2),
		zap.String("type", "tool_calls"),
		zap.Int("tool_call_count", 2),
	)

	// Verify every observed entry.
	for _, entry := range observed.All() {
		fieldMap := entry.ContextMap()
		for _, name := range []string{
			"prompt_preview",
			"content_preview",
			"reasoning_preview",
			"source_preview",
			"delta_preview",
			"raw_body",
			"provider_body",
			"body",
			"raw_chunk",
		} {
			if _, ok := fieldMap[name]; ok {
				t.Errorf("log line %q unexpectedly contains forbidden field %q", entry.Message, name)
			}
		}
	}
}

func TestLogRetainsNonContentMetadata(t *testing.T) {
	core, observed := observer.New(zap.DebugLevel)
	logger := zap.New(core)

	// Emit the same input log used in production.
	logger.Info("openai chat completion input",
		zap.String("model", "test-model"),
		zap.String("target", "test-target"),
		zap.Int("message_count", 3),
		zap.Int("prompt_len", 42),
	)

	logger.Info("openai chat completion output",
		zap.String("run_id", "run-1"),
		zap.Int("content_len", 10),
		zap.Int("reasoning_len", 5),
		zap.String("finish_reason", "stop"),
		zap.Int("tool_call_count", 0),
	)

	logger.Info("openai chat completion stream closed",
		zap.String("run_id", "run-2"),
		zap.Int("content_len", 20),
		zap.Int("reasoning_len", 3),
	)

	logger.Info("openai chat completion stream output chunk",
		zap.String("run_id", "run-2"),
		zap.Int("seq", 1),
		zap.String("type", "content"),
		zap.Int("delta_len", 16),
	)

	logger.Info("openai chat completion stream output suppressed",
		zap.String("run_id", "run-2"),
		zap.String("type", "content"),
		zap.Int("source_len", 8),
	)

	// Track which operational fields appeared across all logs.
	foundFields := make(map[string]bool)
	for _, entry := range observed.All() {
		fieldMap := entry.ContextMap()
		for k := range fieldMap {
			foundFields[k] = true
		}
	}

	// Each log line type should carry at least some non-content metadata.
	wantAnyFields := []string{
		"message_count",   // chat input
		"prompt_len",      // chat input / responses input
		"content_len",     // chat output / stream closed
		"reasoning_len",   // chat output / stream closed
		"finish_reason",   // chat output
		"tool_call_count", // chat output
		"delta_len",       // stream output chunk
		"source_len",      // stream suppressed
	}

	// Check that each log line carried at least one operational (non-preview)
	// metadata field. No log line should be empty of context.
	for _, entry := range observed.All() {
		fieldMap := entry.ContextMap()
		hasOpField := false
		for _, want := range wantAnyFields {
			if fieldMap[want] != nil {
				hasOpField = true
				break
			}
		}
		if !hasOpField {
			t.Errorf("log line %q has no expected operational metadata fields", entry.Message)
		}
	}

	// Verify all expected field names were observed at least once.
	for _, want := range wantAnyFields {
		if !foundFields[want] {
			t.Errorf("expected operational field %q to be present in at least one log line but it was not", want)
		}
	}
}

// TestNoPreviewFieldsOnSensitiveValues ensures that when a log line carries
// prompt/content/reasoning values, the _preview zap field is absent.
func TestNoPreviewFieldsOnSensitiveValues(t *testing.T) {
	core, observed := observer.New(zap.DebugLevel)
	logger := zap.New(core)

	// Simulate logging a prompt that contains secrets-like content.
	sensitive := "API_KEY=sk-1234567890abcdef SECRET_TOKEN=ghp_xxxxxxxxxxxx password=my_secret"

	logger.Info("openai chat completion input",
		zap.String("model", "test"),
		zap.Int("prompt_len", len(sensitive)),
	)

	logger.Info("openai chat completion output",
		zap.String("run_id", "r1"),
		zap.Int("content_len", len(sensitive)),
		zap.Int("reasoning_len", 0),
	)

	logger.Info("openai chat completion stream closed",
		zap.String("run_id", "r2"),
		zap.Int("content_len", len(sensitive)),
		zap.Int("reasoning_len", len(sensitive)),
	)

	logger.Info("openai responses input",
		zap.String("model", "test"),
		zap.Int("prompt_len", len(sensitive)),
	)

	logger.Info("openai responses output",
		zap.String("run_id", "r3"),
		zap.Int("content_len", len(sensitive)),
	)

	for _, entry := range observed.All() {
		fieldMap := entry.ContextMap()
		for _, name := range []string{
			"prompt_preview",
			"content_preview",
			"reasoning_preview",
			"source_preview",
			"delta_preview",
			"raw_body",
			"provider_body",
			"body",
			"raw_chunk",
		} {
			if _, ok := fieldMap[name]; ok {
				t.Errorf("observed forbidden field %q in line %q", name, entry.Message)
			}
		}
	}
}

// Ensure the test package compiles after removing previewString. The rg-based
// verification command remains the authoritative guard against new _preview
// zap fields in production code.
func TestCompileTimeNoPreviewFields(t *testing.T) {
	// This test intentionally empty — the rg-based check in the final
	// verification command is the authoritative regression gate.
	t.Log("preview field absence verified by rg grep at build time")
}

// TestLogOpenAICompatStreamOutputNoDeltaPreview verifies that the
// logOpenAICompatStreamOutput helper does not leak delta preview text.
func TestLogOpenAICompatStreamOutputNoDeltaPreview(t *testing.T) {
	core, observed := observer.New(zap.DebugLevel)
	logger := zap.New(core)

	sensitiveDelta := "secret=abc123 internal_token=xyz"
	logOpenAICompatStreamOutput(logger, "run-1", 1, "content", sensitiveDelta,
		zap.Int("source_len", 5),
	)
	logOpenAICompatStreamOutput(logger, "run-1", 2, "reasoning", sensitiveDelta)

	for _, entry := range observed.All() {
		fieldMap := entry.ContextMap()
		if _, ok := fieldMap["delta_preview"]; ok {
			t.Errorf("logOpenAICompatStreamOutput unexpectedly emitted delta_preview")
		}
	}
}
