package httpapi

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log/slog"
	"net/http"
	"net/http/httptest"
	"os"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"testing"
	"time"

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

func TestKelingOmniCompatibleHTTPFlow(t *testing.T) {
	databaseURL := strings.TrimSpace(os.Getenv("AI_GATEWAY_TEST_DATABASE_URL"))
	if databaseURL == "" {
		t.Skip("set AI_GATEWAY_TEST_DATABASE_URL to run the Kling-compatible HTTP integration flow")
	}

	var upstreamTaskSequence atomic.Int64
	var upstreamPayloadMu sync.Mutex
	var upstreamPayloads []map[string]any
	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Authorization") != "Bearer upstream-keling-key" {
			t.Fatalf("unexpected upstream Authorization: %q", r.Header.Get("Authorization"))
		}
		switch {
		case r.Method == http.MethodPost && r.URL.Path == "/videos/omni-video":
			var payload map[string]any
			if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
				t.Fatalf("decode upstream request: %v", err)
			}
			upstreamPayloadMu.Lock()
			upstreamPayloads = append(upstreamPayloads, payload)
			upstreamPayloadMu.Unlock()
			id := "upstream-" + strconv.FormatInt(upstreamTaskSequence.Add(1), 10)
			_ = json.NewEncoder(w).Encode(map[string]any{
				"code":       0,
				"request_id": "submit-" + id,
				"data":       map[string]any{"task_id": id, "task_status": "submitted"},
			})
		case r.Method == http.MethodGet && strings.HasPrefix(r.URL.Path, "/videos/omni-video/upstream-"):
			id := strings.TrimPrefix(r.URL.Path, "/videos/omni-video/")
			_ = json.NewEncoder(w).Encode(map[string]any{
				"code":       0,
				"request_id": "poll-" + id,
				"data": map[string]any{
					"task_id":     id,
					"task_status": "succeed",
					"created_at":  time.Now().UnixMilli(),
					"task_result": map[string]any{"videos": []any{map[string]any{
						"id":            "video-" + id,
						"url":           "https://example.com/" + id + ".mp4",
						"watermark_url": "https://example.com/" + id + "-watermark.mp4",
						"duration":      "3",
					}}},
				},
			})
		default:
			t.Fatalf("unexpected upstream request %s %s", r.Method, r.URL.Path)
		}
	}))
	defer upstream.Close()

	ctx := context.Background()
	applyMigration(t, ctx, databaseURL)
	db, err := store.Connect(ctx, databaseURL)
	if err != nil {
		t.Fatalf("connect store: %v", err)
	}
	defer db.Close()

	suffix := strconv.FormatInt(time.Now().UnixNano(), 10)
	platform, err := db.CreatePlatform(ctx, store.CreatePlatformInput{
		Provider:    "keling",
		PlatformKey: "keling-compatible-test-" + suffix,
		Name:        "Kling Compatible Test",
		BaseURL:     upstream.URL,
		AuthType:    "APIKey",
		Credentials: map[string]any{"apiKey": "upstream-keling-key"},
		Config: map[string]any{
			"kelingPollIntervalMs":     100,
			"kelingPollTimeoutSeconds": 5,
		},
		Priority: 1,
		Status:   "enabled",
	})
	if err != nil {
		t.Fatalf("create test platform: %v", err)
	}
	_, err = db.CreatePlatformModel(ctx, store.CreatePlatformModelInput{
		PlatformID:        platform.ID,
		CanonicalModelKey: "keling:kling-video-o1",
		ModelName:         "kling-video-o1",
		ProviderModelName: "kling-video-o1",
		ModelAlias:        "",
		ModelType:         store.StringList{"omni_video", "video_generate"},
		DisplayName:       "Kling O1 Compatible Test",
		Capabilities: map[string]any{
			"omni_video": map[string]any{
				"supported_modes":      []any{"text_to_video", "image_reference"},
				"output_resolutions":   []any{"720p", "1080p"},
				"aspect_ratio_allowed": []any{"16:9", "9:16", "1:1"},
				"duration_options":     []any{3, 4, 5, 6, 7, 8, 9, 10},
				"output_audio":         false,
				"max_images":           7,
			},
			"video_generate": map[string]any{
				"supported_modes":      []any{"text_to_video"},
				"output_resolutions":   []any{"720p", "1080p"},
				"aspect_ratio_allowed": []any{"16:9", "9:16", "1:1"},
				"duration_options":     []any{3, 4, 5, 6, 7, 8, 9, 10},
				"output_audio":         true,
			},
		},
		Enabled: true,
	})
	if err != nil {
		t.Fatalf("create test platform model: %v", err)
	}

	serverCtx, cancelServer := context.WithCancel(ctx)
	defer cancelServer()
	gateway := httptest.NewServer(NewServerWithContext(serverCtx, config.Config{
		AppEnv:                            "test",
		HTTPAddr:                          ":0",
		DatabaseURL:                       databaseURL,
		IdentityMode:                      "hybrid",
		JWTSecret:                         "test-secret",
		CORSAllowedOrigin:                 "*",
		AsyncQueueWorkerEnabled:           true,
		AsyncWorkerHardLimit:              32,
		AsyncWorkerInstanceHardLimit:      32,
		AsyncWorkerRefreshIntervalSeconds: 1,
	}, db, slog.New(slog.NewTextHandler(io.Discard, nil))))
	defer gateway.Close()

	firstUserToken, firstAPIKey := createKelingCompatIntegrationUser(t, ctx, db, gateway.URL, "first", suffix, true)
	_ = firstUserToken
	_, secondAPIKey := createKelingCompatIntegrationUser(t, ctx, db, gateway.URL, "second", suffix, false)

	var created KelingCompatibleEnvelope
	doJSON(t, gateway.URL, http.MethodPost, "/api/v1/videos/omni-video", firstAPIKey, map[string]any{
		"model_name":       "kling-video-o1",
		"prompt":           "A clean product reveal",
		"mode":             "std",
		"aspect_ratio":     "16:9",
		"duration":         "3",
		"sound":            "off",
		"image_list":       []any{map[string]any{"image_url": gateway.URL + "/static/simulation/image.png"}},
		"external_task_id": "compat-http-1",
	}, http.StatusOK, &created)
	createdData, _ := created.Data.(map[string]any)
	if created.Code != 0 || created.RequestID == "" || strings.TrimSpace(stringFromKelingCompat(createdData["task_id"])) == "" || createdData["task_status"] != "submitted" {
		t.Fatalf("unexpected compatible create response: %+v", created)
	}
	taskID := stringFromKelingCompat(createdData["task_id"])

	var hidden KelingCompatibleEnvelope
	doJSON(t, gateway.URL, http.MethodGet, "/api/v1/videos/omni-video/"+taskID, secondAPIKey, nil, http.StatusNotFound, &hidden)
	if hidden.Code != 1203 {
		t.Fatalf("cross-user task must be hidden: %+v", hidden)
	}

	completed := waitForKelingCompatTask(t, gateway.URL, firstAPIKey, taskID, 5*time.Second)
	if completed.Code != 0 {
		t.Fatalf("compatible task failed: %+v", completed)
	}
	completedData, _ := completed.Data.(map[string]any)
	if completedData["task_status"] != "succeed" {
		t.Fatalf("compatible task did not succeed: %+v", completedData)
	}
	taskResult, _ := completedData["task_result"].(map[string]any)
	videos, _ := taskResult["videos"].([]any)
	video, _ := videos[0].(map[string]any)
	if video["id"] == "" || video["watermark_url"] == "" || video["duration"] != "3" {
		t.Fatalf("compatible result lost video metadata: %+v", video)
	}

	var standard struct {
		TaskID string `json:"taskId"`
	}
	doJSONWithHeaders(t, gateway.URL, http.MethodPost, "/api/v1/videos/generations", firstAPIKey, map[string]any{
		"model":        "kling-o1",
		"prompt":       "A second product reveal",
		"resolution":   "720p",
		"aspect_ratio": "16:9",
		"duration":     3,
		"audio":        false,
	}, map[string]string{"X-Async": "true"}, http.StatusAccepted, &standard)
	if standard.TaskID == "" {
		t.Fatal("standard video generation did not return taskId")
	}
	waitForTaskStatus(t, gateway.URL, firstAPIKey, standard.TaskID, []string{"succeeded"}, 5*time.Second)

	var unsupportedAudio struct {
		TaskID string `json:"taskId"`
	}
	doJSONWithHeaders(t, gateway.URL, http.MethodPost, "/api/v1/videos/generations", firstAPIKey, map[string]any{
		"model":        "kling-o1",
		"prompt":       "An O1 request that must not silently ignore audio",
		"resolution":   "1080p",
		"aspect_ratio": "9:16",
		"duration":     5,
		"audio":        true,
	}, map[string]string{"X-Async": "true"}, http.StatusAccepted, &unsupportedAudio)
	if unsupportedAudio.TaskID == "" {
		t.Fatal("unsupported O1 audio request did not return taskId")
	}
	waitForTaskStatus(t, gateway.URL, firstAPIKey, unsupportedAudio.TaskID, []string{"failed"}, 5*time.Second)
	var failedAudioTask store.GatewayTask
	doJSON(t, gateway.URL, http.MethodGet, "/api/v1/tasks/"+unsupportedAudio.TaskID, firstAPIKey, nil, http.StatusOK, &failedAudioTask)
	if failedAudioTask.ErrorCode != "invalid_parameter" || !strings.Contains(failedAudioTask.ErrorMessage, "does not support generated audio") {
		t.Fatalf("O1 audio request must fail visibly before upstream submission: %+v", failedAudioTask)
	}

	upstreamPayloadMu.Lock()
	defer upstreamPayloadMu.Unlock()
	if len(upstreamPayloads) != 2 {
		t.Fatalf("expected two upstream submissions, got %d", len(upstreamPayloads))
	}
	compatiblePayload := upstreamPayloads[0]
	if compatiblePayload["model_name"] != "kling-video-o1" || compatiblePayload["mode"] != "std" || compatiblePayload["sound"] != "off" || compatiblePayload["duration"] != "3" || compatiblePayload["aspect_ratio"] != "16:9" || compatiblePayload["external_task_id"] != "compat-http-1" {
		t.Fatalf("unexpected compatible upstream payload: %+v", compatiblePayload)
	}
}

func createKelingCompatIntegrationUser(t *testing.T, ctx context.Context, db *store.Store, baseURL string, prefix string, suffix string, fund bool) (string, string) {
	t.Helper()
	username := fmt.Sprintf("kling_compat_%s_%s", prefix, suffix)
	password := "password123"
	var registered struct {
		AccessToken string `json:"accessToken"`
	}
	doJSON(t, baseURL, http.MethodPost, "/api/v1/auth/register", "", map[string]any{
		"username": username,
		"email":    username + "@example.com",
		"password": password,
	}, http.StatusCreated, &registered)
	var apiKey struct {
		Secret string `json:"secret"`
	}
	doJSON(t, baseURL, http.MethodPost, "/api/v1/api-keys", registered.AccessToken, map[string]any{
		"name":   "Kling compatible integration key",
		"scopes": []string{"video"},
	}, http.StatusCreated, &apiKey)
	if fund {
		if _, err := db.Pool().Exec(ctx, `UPDATE gateway_users SET roles = '["admin"]'::jsonb WHERE username = $1`, username); err != nil {
			t.Fatalf("promote integration user: %v", err)
		}
		var loggedIn struct {
			AccessToken string `json:"accessToken"`
		}
		doJSON(t, baseURL, http.MethodPost, "/api/v1/auth/login", "", map[string]any{
			"account":  username,
			"password": password,
		}, http.StatusOK, &loggedIn)
		var gatewayUserID string
		if err := db.Pool().QueryRow(ctx, `SELECT id::text FROM gateway_users WHERE username = $1`, username).Scan(&gatewayUserID); err != nil {
			t.Fatalf("read integration user id: %v", err)
		}
		doJSON(t, baseURL, http.MethodPatch, "/api/admin/users/"+gatewayUserID+"/wallet", loggedIn.AccessToken, map[string]any{
			"currency": "resource",
			"balance":  1000,
			"reason":   "seed Kling compatible integration wallet",
		}, http.StatusOK, nil)
	}
	return registered.AccessToken, apiKey.Secret
}

func waitForKelingCompatTask(t *testing.T, baseURL string, apiKey string, taskID string, timeout time.Duration) KelingCompatibleEnvelope {
	t.Helper()
	deadline := time.Now().Add(timeout)
	for time.Now().Before(deadline) {
		var response KelingCompatibleEnvelope
		doJSON(t, baseURL, http.MethodGet, "/api/v1/videos/omni-video/"+taskID, apiKey, nil, http.StatusOK, &response)
		data, _ := response.Data.(map[string]any)
		switch data["task_status"] {
		case "succeed":
			return response
		case "failed":
			t.Fatalf("Kling-compatible task failed: %+v", response)
		}
		time.Sleep(50 * time.Millisecond)
	}
	t.Fatalf("timed out waiting for Kling-compatible task %s", taskID)
	return KelingCompatibleEnvelope{}
}
