package runner

import (
	"context"
	"encoding/base64"
	"errors"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/easyai/easyai-ai-gateway/apps/api/internal/clients"
	"github.com/easyai/easyai-ai-gateway/apps/api/internal/config"
	"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)

func TestHydrateProviderRequestAssetsConvertsStrictBase64Field(t *testing.T) {
	storageDir := t.TempDir()
	fileName := "gateway-request-asset-test.png"
	if err := os.WriteFile(filepath.Join(storageDir, fileName), []byte("image bytes"), 0o644); err != nil {
		t.Fatalf("write request asset: %v", err)
	}
	service := &Service{cfg: config.Config{LocalUploadedStorageDir: storageDir}}
	body := map[string]any{
		"model": "demo",
		"b64_json": map[string]any{
			"assetRef": map[string]any{
				"sha256":          "sha-test",
				"contentType":     "image/png",
				"url":             "/static/uploaded/" + fileName,
				"storageProvider": "local_static",
			},
			"url": "/static/uploaded/" + fileName,
		},
	}

	hydrated, err := service.hydrateProviderRequestAssets(context.Background(), body, store.RuntimeModelCandidate{})
	if err != nil {
		t.Fatalf("hydrate request assets: %v", err)
	}
	if got, want := stringFromAny(hydrated["b64_json"]), base64.StdEncoding.EncodeToString([]byte("image bytes")); got != want {
		t.Fatalf("unexpected hydrated base64: got %q want %q", got, want)
	}
}

func TestHydrateProviderRequestAssetsConvertsInputAudioDataToRawBase64(t *testing.T) {
	storageDir := t.TempDir()
	fileName := "gateway-request-asset-audio.mp3"
	payload := []byte("audio bytes")
	if err := os.WriteFile(filepath.Join(storageDir, fileName), payload, 0o644); err != nil {
		t.Fatalf("write request asset: %v", err)
	}
	service := &Service{cfg: config.Config{LocalUploadedStorageDir: storageDir}}
	body := map[string]any{
		"messages": []any{
			map[string]any{
				"role": "user",
				"content": []any{
					map[string]any{
						"type": "input_audio",
						"input_audio": map[string]any{
							"data": map[string]any{
								"assetRef": map[string]any{
									"sha256":          "sha-input-audio",
									"contentType":     "audio/mpeg",
									"url":             "/static/uploaded/" + fileName,
									"storageProvider": "local_static",
								},
								"url": "/static/uploaded/" + fileName,
							},
							"format": "mp3",
						},
					},
				},
			},
		},
	}

	hydrated, err := service.hydrateProviderRequestAssets(context.Background(), body, store.RuntimeModelCandidate{})
	if err != nil {
		t.Fatalf("hydrate request assets: %v", err)
	}
	messages := hydrated["messages"].([]any)
	message := messages[0].(map[string]any)
	content := message["content"].([]any)
	audioPart := content[0].(map[string]any)
	inputAudio := audioPart["input_audio"].(map[string]any)
	if got, want := stringFromAny(inputAudio["data"]), base64.StdEncoding.EncodeToString(payload); got != want {
		t.Fatalf("unexpected hydrated input audio: got %q want %q", got, want)
	}
	if inputAudio["format"] != "mp3" {
		t.Fatalf("input audio format was not preserved: %+v", inputAudio)
	}
}

func TestHydrateProviderRequestAssetsConvertsBase64ArrayField(t *testing.T) {
	storageDir := t.TempDir()
	fileName := "gateway-request-asset-array.png"
	if err := os.WriteFile(filepath.Join(storageDir, fileName), []byte("array image bytes"), 0o644); err != nil {
		t.Fatalf("write request asset: %v", err)
	}
	service := &Service{cfg: config.Config{LocalUploadedStorageDir: storageDir}}
	body := map[string]any{
		"binary_data_base64": []any{
			map[string]any{
				"assetRef": map[string]any{
					"sha256":          "sha-array-image",
					"contentType":     "image/png",
					"url":             "/static/uploaded/" + fileName,
					"storageProvider": "local_static",
				},
				"url": "/static/uploaded/" + fileName,
			},
		},
	}

	hydrated, err := service.hydrateProviderRequestAssets(context.Background(), body, store.RuntimeModelCandidate{})
	if err != nil {
		t.Fatalf("hydrate request assets: %v", err)
	}
	values := hydrated["binary_data_base64"].([]any)
	if got, want := stringFromAny(values[0]), base64.StdEncoding.EncodeToString([]byte("array image bytes")); got != want {
		t.Fatalf("unexpected hydrated array base64: got %q want %q", got, want)
	}
}

func TestHydrateProviderRequestAssetsConvertsGeminiInlineDataAssetToRawBase64(t *testing.T) {
	storageDir := t.TempDir()
	fileName := "gateway-request-asset-gemini-inline.png"
	payload := []byte("gemini inline image bytes")
	if err := os.WriteFile(filepath.Join(storageDir, fileName), payload, 0o644); err != nil {
		t.Fatalf("write request asset: %v", err)
	}
	service := &Service{cfg: config.Config{LocalUploadedStorageDir: storageDir}}
	body := map[string]any{
		"contents": []any{
			map[string]any{
				"role": "user",
				"parts": []any{
					map[string]any{
						"inlineData": map[string]any{
							"mimeType": "image/png",
							"data": map[string]any{
								"assetRef": map[string]any{
									"sha256":          "sha-gemini-inline",
									"contentType":     "image/png",
									"url":             "/static/uploaded/" + fileName,
									"storageProvider": "local_static",
								},
								"url": "/static/uploaded/" + fileName,
							},
						},
					},
				},
			},
		},
	}

	hydrated, err := service.hydrateProviderRequestAssets(context.Background(), body, store.RuntimeModelCandidate{Provider: "gemini"})
	if err != nil {
		t.Fatalf("hydrate request assets: %v", err)
	}
	contents := hydrated["contents"].([]any)
	content := contents[0].(map[string]any)
	parts := content["parts"].([]any)
	part := parts[0].(map[string]any)
	inlineData := part["inlineData"].(map[string]any)
	if got, want := stringFromAny(inlineData["data"]), base64.StdEncoding.EncodeToString(payload); got != want {
		t.Fatalf("unexpected hydrated inlineData base64: got %q want %q", got, want)
	}
}

func TestGeminiVeoPreservesURLUntilOfficialPayloadConstruction(t *testing.T) {
	candidate := store.RuntimeModelCandidate{
		Provider:          "gemini",
		ProviderModelName: "veo-3.1-generate-preview",
		ModelType:         "image_to_video",
		Capabilities: map[string]any{
			"image_to_video": map[string]any{
				"support_url_input":    true,
				"support_base64_input": true,
			},
		},
	}
	asset := store.RequestAsset{URL: "https://cdn.example.com/input.png", StorageProvider: "remote", ContentType: "image/png"}
	if got := requestAssetHydrationForField([]string{"image"}, asset, candidate); got != requestAssetHydrateURL {
		t.Fatalf("Gemini Veo should preserve public URLs until client payload construction, got %q", got)
	}
}

func TestRemoteMediaMaterializationBoundaryByClient(t *testing.T) {
	imageAsset := store.RequestAsset{URL: "https://cdn.example.com/input.png", StorageProvider: "remote", ContentType: "image/png"}
	audioAsset := store.RequestAsset{URL: "https://cdn.example.com/input.mp3", StorageProvider: "remote", ContentType: "audio/mpeg"}
	tests := []struct {
		name      string
		candidate store.RuntimeModelCandidate
		path      []string
		asset     store.RequestAsset
		want      bool
	}{
		{name: "OpenAI multipart image edit", candidate: store.RuntimeModelCandidate{SpecType: "openai", ModelType: "image_edit"}, path: []string{"image"}, asset: imageAsset, want: true},
		{name: "OpenAI JSON URL image edit", candidate: store.RuntimeModelCandidate{SpecType: "openai", ModelType: "image_edit", PlatformConfig: map[string]any{"imageEditRequestFormat": "json_url"}}, path: []string{"image"}, asset: imageAsset, want: false},
		{name: "Gemini Veo", candidate: store.RuntimeModelCandidate{SpecType: "gemini", ModelType: "image_to_video", ProviderModelName: "veo-3.1-generate-preview"}, path: []string{"image"}, asset: imageAsset, want: true},
		{name: "Keling video", candidate: store.RuntimeModelCandidate{SpecType: "keling", ModelType: "image_to_video"}, path: []string{"image"}, asset: imageAsset, want: true},
		{name: "Minimax voice clone", candidate: store.RuntimeModelCandidate{SpecType: "minimax", ModelType: "voice_clone"}, path: []string{"audio"}, asset: audioAsset, want: true},
		{name: "Volces JSON image", candidate: store.RuntimeModelCandidate{SpecType: "volces", ModelType: "image_edit"}, path: []string{"image"}, asset: imageAsset, want: false},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			if got := requestAssetClientMaterializesRemoteMedia(test.candidate, test.path, test.asset); got != test.want {
				t.Fatalf("client materializes remote media = %t, want %t", got, test.want)
			}
		})
	}
}

func TestJSONTaskClientDoesNotForcePublicURLToBase64WithoutCapability(t *testing.T) {
	asset := store.RequestAsset{URL: "https://cdn.example.com/input.png", StorageProvider: "remote", ContentType: "image/png"}
	candidate := store.RuntimeModelCandidate{SpecType: "volces", ModelType: "image_edit"}
	if got := requestAssetHydrationForField([]string{"image"}, asset, candidate); got != requestAssetHydrateURL {
		t.Fatalf("JSON task client should preserve a public URL when no capability requires base64, got %q", got)
	}
}

func TestGeminiURLsArePreservedUntilClientProtocolConstruction(t *testing.T) {
	service := &Service{}
	compatibleURL := "https://cdn.example.com/compatible.png"
	body := map[string]any{"image": compatibleURL}

	compatible, err := service.hydrateProviderRequestAssets(context.Background(), body, store.RuntimeModelCandidate{
		SpecType:  "gemini",
		BaseURL:   "https://gemini-compatible.example.com/v1beta",
		ModelType: "image_edit",
		PlatformConfig: map[string]any{
			"supportUrlInput":    true,
			"supportBase64Input": true,
		},
	})
	if err != nil {
		t.Fatalf("hydrate compatible Gemini image: %v", err)
	}
	if compatible["image"] != compatibleURL {
		t.Fatalf("compatible Gemini URL should be preserved until client inline conversion, got %q", compatible["image"])
	}

	officialURL := "https://cdn.example.com/source.png"
	official, err := service.hydrateProviderRequestAssets(context.Background(), map[string]any{"image": officialURL}, store.RuntimeModelCandidate{
		SpecType:  "gemini",
		BaseURL:   "https://generativelanguage.googleapis.com/v1beta",
		ModelType: "image_edit",
	})
	if err != nil {
		t.Fatalf("hydrate official Gemini image: %v", err)
	}
	if official["image"] != officialURL {
		t.Fatalf("official Gemini should preserve URL for Files API upload, got %q", official["image"])
	}
}

func TestHydrateProviderRequestAssetsConvertsVolcesImageURLAssetToDataURL(t *testing.T) {
	storageDir := t.TempDir()
	fileName := "gateway-request-asset-chat-image.png"
	if err := os.WriteFile(filepath.Join(storageDir, fileName), []byte("chat image bytes"), 0o644); err != nil {
		t.Fatalf("write request asset: %v", err)
	}
	service := &Service{cfg: config.Config{LocalUploadedStorageDir: storageDir}}
	body := map[string]any{
		"messages": []any{
			map[string]any{
				"role": "user",
				"content": []any{
					map[string]any{"type": "text", "text": "describe it"},
					map[string]any{
						"type": "image_url",
						"image_url": map[string]any{
							"url": map[string]any{
								"assetRef": map[string]any{
									"sha256":          "sha-chat-image",
									"contentType":     "image/png",
									"url":             "/static/uploaded/" + fileName,
									"storageProvider": "local_static",
								},
								"url": "/static/uploaded/" + fileName,
							},
						},
					},
				},
			},
		},
	}

	hydrated, err := service.hydrateProviderRequestAssets(context.Background(), body, store.RuntimeModelCandidate{Provider: "volces"})
	if err != nil {
		t.Fatalf("hydrate request assets: %v", err)
	}
	messages := hydrated["messages"].([]any)
	message := messages[0].(map[string]any)
	content := message["content"].([]any)
	imagePart := content[1].(map[string]any)
	imageURL := imagePart["image_url"].(map[string]any)
	if got, want := stringFromAny(imageURL["url"]), "data:image/png;base64,"+base64.StdEncoding.EncodeToString([]byte("chat image bytes")); got != want {
		t.Fatalf("unexpected hydrated image data url: got %q want %q", got, want)
	}
}

func TestHydrateProviderRequestAssetsUsesImageCapabilityBase64ForTopLevelImageAsset(t *testing.T) {
	storageDir := t.TempDir()
	fileName := "gateway-request-asset-edit-image.png"
	payload := []byte("edit image bytes")
	if err := os.WriteFile(filepath.Join(storageDir, fileName), payload, 0o644); err != nil {
		t.Fatalf("write request asset: %v", err)
	}
	service := &Service{cfg: config.Config{LocalUploadedStorageDir: storageDir}}
	body := map[string]any{
		"image": map[string]any{
			"assetRef": map[string]any{
				"sha256":          "sha-edit-image",
				"contentType":     "image/png",
				"url":             "/static/uploaded/" + fileName,
				"storageProvider": "local_static",
			},
			"url": "/static/uploaded/" + fileName,
		},
	}

	hydrated, err := service.hydrateProviderRequestAssets(context.Background(), body, store.RuntimeModelCandidate{
		ModelType: "image_edit",
		Capabilities: map[string]any{
			"image_edit": map[string]any{
				"support_url_input":    false,
				"support_base64_input": true,
			},
		},
	})
	if err != nil {
		t.Fatalf("hydrate request assets: %v", err)
	}
	if got, want := stringFromAny(hydrated["image"]), "data:image/png;base64,"+base64.StdEncoding.EncodeToString(payload); got != want {
		t.Fatalf("unexpected hydrated image data url: got %q want %q", got, want)
	}
}

func TestHydrateProviderRequestAssetsPreservesOpenAIEditURLsUntilMultipartSubmission(t *testing.T) {
	service := &Service{}
	body := map[string]any{
		"images": []any{
			"https://cdn.example.com/first.png",
			"https://cdn.example.com/second.png",
		},
	}

	hydrated, err := service.hydrateProviderRequestAssets(context.Background(), body, store.RuntimeModelCandidate{
		Provider:  "openai",
		ModelType: "image_edit",
		Capabilities: map[string]any{
			"image_edit": map[string]any{
				"support_url_input":    true,
				"support_base64_input": false,
			},
		},
	})
	if err != nil {
		t.Fatalf("hydrate OpenAI edit images: %v", err)
	}
	images := hydrated["images"].([]any)
	if len(images) != 2 || images[0] != "https://cdn.example.com/first.png" || images[1] != "https://cdn.example.com/second.png" {
		t.Fatalf("OpenAI edit URLs should be preserved until multipart construction: %+v", images)
	}
}

func TestHydrateProviderRequestAssetsKeepsURLsForExplicitOpenAIJSONMode(t *testing.T) {
	service := &Service{}
	body := map[string]any{
		"images": []any{
			"https://cdn.example.com/first.png",
			"https://cdn.example.com/second.png",
		},
	}
	candidate := store.RuntimeModelCandidate{
		Provider:  "compatible-openai",
		SpecType:  "openai",
		ModelType: "image_edit",
		Capabilities: map[string]any{
			"image_edit": map[string]any{
				"support_url_input":    false,
				"support_base64_input": true,
			},
		},
		PlatformConfig: map[string]any{"imageEditRequestFormat": "json_url"},
	}

	hydrated, err := service.hydrateProviderRequestAssets(context.Background(), body, candidate)
	if err != nil {
		t.Fatalf("hydrate OpenAI JSON edit images: %v", err)
	}
	images := hydrated["images"].([]any)
	if len(images) != 2 || images[0] != "https://cdn.example.com/first.png" || images[1] != "https://cdn.example.com/second.png" {
		t.Fatalf("OpenAI JSON mode should preserve public URLs: %+v", images)
	}
	asset := store.RequestAsset{URL: "https://cdn.example.com/asset.png", StorageProvider: "remote", ContentType: "image/png"}
	if got := requestAssetHydrationForField([]string{"image"}, asset, candidate); got != requestAssetHydrateURL {
		t.Fatalf("explicit JSON mode must override base64 capability hydration, got %q", got)
	}
}

func TestHydrateProviderRequestAssetsRejectsInlineDataForExplicitOpenAIJSONMode(t *testing.T) {
	service := &Service{}
	_, err := service.hydrateProviderRequestAssets(context.Background(), map[string]any{
		"image": "data:image/png;base64,aW1hZ2U=",
	}, store.RuntimeModelCandidate{
		Provider:       "openai",
		ModelType:      "image_edit",
		PlatformConfig: map[string]any{"imageEditRequestFormat": "json_url"},
	})
	clientErr, ok := err.(*clients.ClientError)
	if !ok || clientErr.Code != "request_asset_public_url_required" || clientErr.Param != "image" || clientErr.Retryable {
		t.Fatalf("expected public URL validation error, got %#v", err)
	}
}

func TestHydrateProviderRequestAssetsImageCapabilityOverridesProviderDataURLDefault(t *testing.T) {
	service := &Service{}
	body := map[string]any{
		"messages": []any{
			map[string]any{
				"role": "user",
				"content": []any{
					map[string]any{
						"type": "image_url",
						"image_url": map[string]any{
							"url": map[string]any{
								"assetRef": map[string]any{
									"sha256":          "sha-url-only-image",
									"contentType":     "image/png",
									"url":             "https://cdn.example.com/request.png",
									"storageProvider": "remote",
								},
								"url": "https://cdn.example.com/request.png",
							},
						},
					},
				},
			},
		},
	}

	hydrated, err := service.hydrateProviderRequestAssets(context.Background(), body, store.RuntimeModelCandidate{
		Provider:  "volces",
		ModelType: "image_edit",
		Capabilities: map[string]any{
			"image_edit": map[string]any{
				"support_url_input":    true,
				"support_base64_input": false,
			},
		},
	})
	if err != nil {
		t.Fatalf("hydrate request assets: %v", err)
	}
	messages := hydrated["messages"].([]any)
	message := messages[0].(map[string]any)
	content := message["content"].([]any)
	imagePart := content[0].(map[string]any)
	imageURL := imagePart["image_url"].(map[string]any)
	if got, want := stringFromAny(imageURL["url"]), "https://cdn.example.com/request.png"; got != want {
		t.Fatalf("image capability should keep URL despite provider default, got %q want %q", got, want)
	}
}

func TestHydrateProviderRequestAssetsUsesBase64ForLocalAssetEvenWhenModelSupportsURL(t *testing.T) {
	storageDir := t.TempDir()
	fileName := "gateway-request-asset-local-image.png"
	payload := []byte("local image bytes")
	if err := os.WriteFile(filepath.Join(storageDir, fileName), payload, 0o644); err != nil {
		t.Fatalf("write request asset: %v", err)
	}
	service := &Service{cfg: config.Config{LocalUploadedStorageDir: storageDir}}
	body := map[string]any{
		"image": map[string]any{
			"assetRef": map[string]any{
				"sha256":          "sha-local-image",
				"contentType":     "image/png",
				"url":             "/static/uploaded/" + fileName,
				"storageProvider": "local_static",
			},
			"url": "/static/uploaded/" + fileName,
		},
	}

	hydrated, err := service.hydrateProviderRequestAssets(context.Background(), body, store.RuntimeModelCandidate{
		ModelType: "image_edit",
		Capabilities: map[string]any{
			"image_edit": map[string]any{
				"support_url_input":    true,
				"support_base64_input": true,
			},
		},
	})
	if err != nil {
		t.Fatalf("hydrate request assets: %v", err)
	}
	if got, want := stringFromAny(hydrated["image"]), "data:image/png;base64,"+base64.StdEncoding.EncodeToString(payload); got != want {
		t.Fatalf("local asset should fall back to base64, got %q want %q", got, want)
	}
}

func TestHydrateProviderRequestAssetsConvertsPlainImageURLWhenModelRequiresBase64(t *testing.T) {
	payload := []byte{0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n', 0, 0, 0, 0}
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "image/png")
		_, _ = w.Write(payload)
	}))
	defer server.Close()
	service := &Service{}
	body := map[string]any{"image": server.URL + "/source.png"}

	hydrated, err := service.hydrateProviderRequestAssets(context.Background(), body, store.RuntimeModelCandidate{
		ModelType: "image_edit",
		Capabilities: map[string]any{
			"image_edit": map[string]any{
				"support_url_input":    false,
				"support_base64_input": true,
			},
		},
	})
	if err != nil {
		t.Fatalf("hydrate request assets: %v", err)
	}
	if got, want := stringFromAny(hydrated["image"]), "data:image/png;base64,"+base64.StdEncoding.EncodeToString(payload); got != want {
		t.Fatalf("plain image URL should be converted to data URL, got %q want %q", got, want)
	}
}

func TestHydrateProviderRequestAssetsConvertsPrivateImageURLEvenWhenModelSupportsURL(t *testing.T) {
	payload := []byte{0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n', 1, 2, 3, 4}
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "image/png")
		_, _ = w.Write(payload)
	}))
	defer server.Close()
	service := &Service{}
	body := map[string]any{"image": server.URL + "/source.png"}

	hydrated, err := service.hydrateProviderRequestAssets(context.Background(), body, store.RuntimeModelCandidate{
		ModelType: "image_edit",
		Capabilities: map[string]any{
			"image_edit": map[string]any{
				"support_url_input":    true,
				"support_base64_input": true,
			},
		},
	})
	if err != nil {
		t.Fatalf("hydrate request assets: %v", err)
	}
	if got, want := stringFromAny(hydrated["image"]), "data:image/png;base64,"+base64.StdEncoding.EncodeToString(payload); got != want {
		t.Fatalf("private image URL should fall back to data URL, got %q want %q", got, want)
	}
}

func TestHydrateProviderRequestAssetsKeepsImageURLAssetAsURLForProviderURLDefault(t *testing.T) {
	service := &Service{}
	body := map[string]any{
		"messages": []any{
			map[string]any{
				"role": "user",
				"content": []any{
					map[string]any{
						"type": "image_url",
						"image_url": map[string]any{
							"url": map[string]any{
								"assetRef": map[string]any{
									"sha256":          "sha-url-image",
									"contentType":     "image/png",
									"url":             "https://cdn.example.com/request.png",
									"storageProvider": "remote",
								},
								"url": "https://cdn.example.com/request.png",
							},
						},
					},
				},
			},
		},
	}

	hydrated, err := service.hydrateProviderRequestAssets(context.Background(), body, store.RuntimeModelCandidate{Provider: "jimeng"})
	if err != nil {
		t.Fatalf("hydrate request assets: %v", err)
	}
	messages := hydrated["messages"].([]any)
	message := messages[0].(map[string]any)
	content := message["content"].([]any)
	imagePart := content[0].(map[string]any)
	imageURL := imagePart["image_url"].(map[string]any)
	if got, want := stringFromAny(imageURL["url"]), "https://cdn.example.com/request.png"; got != want {
		t.Fatalf("unexpected hydrated image URL: got %q want %q", got, want)
	}
}

func TestHydrateProviderRequestAssetsReturnsExpiredError(t *testing.T) {
	expiredAt := time.Now().Add(-time.Minute).UTC().Format(time.RFC3339)
	service := &Service{}
	body := map[string]any{
		"image_url": map[string]any{
			"assetRef": map[string]any{
				"sha256":          "sha-expired",
				"contentType":     "image/png",
				"url":             "/static/uploaded/gateway-request-asset-expired.png",
				"storageProvider": "local_static",
				"expiresAt":       expiredAt,
			},
			"url": "/static/uploaded/gateway-request-asset-expired.png",
		},
	}

	_, err := service.hydrateProviderRequestAssets(context.Background(), body, store.RuntimeModelCandidate{})
	if err == nil {
		t.Fatal("expected expired request asset error")
	}
	var clientErr *clients.ClientError
	if !errors.As(err, &clientErr) || clientErr.Code != "request_asset_expired" {
		t.Fatalf("expected request_asset_expired, got %T %v", err, err)
	}
}

func TestStringFromAnyReadsRequestAssetWrapperURL(t *testing.T) {
	wrapper := map[string]any{
		"assetRef": map[string]any{"url": "https://cdn.example/request.png"},
	}
	if got := stringFromAny(wrapper); got != "https://cdn.example/request.png" {
		t.Fatalf("expected wrapper URL, got %q", got)
	}
}

func TestSlimTaskRequestSnapshotKeepsMessageRefs(t *testing.T) {
	service := &Service{}
	task := store.GatewayTask{Request: map[string]any{
		"conversationId":  "conv-1",
		"messageRefs":     []any{map[string]any{"messageId": "msg-1", "position": 0}},
		"newMessageCount": 1,
	}}
	body := map[string]any{
		"model":    "demo",
		"messages": []any{map[string]any{"role": "user", "content": "hello"}},
	}

	snapshot := service.slimTaskRequestSnapshot(task, body)

	if snapshot["messages"] != nil {
		t.Fatalf("snapshot should not persist restored messages: %+v", snapshot)
	}
	if snapshot["messageRefs"] == nil || snapshot["newMessageCount"] != 1 {
		t.Fatalf("snapshot should keep message refs and new count: %+v", snapshot)
	}
}
