package httpapi

import (
	"context"
	"io"
	"log/slog"
	"net/http"
	"net/http/httptest"
	"os"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/easyai/easyai-ai-gateway/apps/api/internal/config"
	"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
	"github.com/jackc/pgx/v5/pgxpool"
)

func TestAPIKeyAssignableModelsIgnoreAPIKeyRules(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 API key assignable-model integration flow")
	}
	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()

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

	suffixText := strconv.FormatInt(time.Now().UnixNano(), 10)
	username := "api_key_assignable_" + suffixText
	password := "password123"
	var registerResponse struct {
		AccessToken string `json:"accessToken"`
	}
	doJSON(t, server.URL, http.MethodPost, "/api/v1/auth/register", "", map[string]any{
		"username": username,
		"email":    username + "@example.com",
		"password": password,
	}, http.StatusCreated, &registerResponse)

	testPool, err := pgxpool.New(ctx, databaseURL)
	if err != nil {
		t.Fatalf("connect test pool: %v", err)
	}
	defer testPool.Close()
	if _, err := testPool.Exec(ctx, `UPDATE gateway_users SET roles = '["admin"]'::jsonb WHERE username = $1`, username); err != nil {
		t.Fatalf("promote test user: %v", err)
	}

	var loginResponse struct {
		AccessToken string `json:"accessToken"`
	}
	doJSON(t, server.URL, http.MethodPost, "/api/v1/auth/login", "", map[string]any{
		"account":  username,
		"password": password,
	}, http.StatusOK, &loginResponse)

	createAPIKey := func(name string) string {
		t.Helper()
		var response struct {
			APIKey struct {
				ID string `json:"id"`
			} `json:"apiKey"`
		}
		doJSON(t, server.URL, http.MethodPost, "/api/v1/api-keys", loginResponse.AccessToken, map[string]any{
			"name": name,
		}, http.StatusCreated, &response)
		return response.APIKey.ID
	}
	firstAPIKeyID := createAPIKey("first assignable key")
	secondAPIKeyID := createAPIKey("second assignable key")

	var platform struct {
		ID string `json:"id"`
	}
	doJSON(t, server.URL, http.MethodPost, "/api/admin/platforms", loginResponse.AccessToken, map[string]any{
		"provider":    "openai",
		"platformKey": "api-key-assignable-" + suffixText,
		"name":        "API Key Assignable Test",
		"baseUrl":     "https://api.openai.com/v1",
		"authType":    "bearer",
		"credentials": map[string]any{"mode": "simulation"},
		"config":      map[string]any{"testMode": true},
	}, http.StatusCreated, &platform)

	var model struct {
		ID string `json:"id"`
	}
	modelName := "api-key-assignable-model-" + suffixText
	doJSON(t, server.URL, http.MethodPost, "/api/admin/platforms/"+platform.ID+"/models", loginResponse.AccessToken, map[string]any{
		"canonicalModelKey": "openai:gpt-4o-mini",
		"modelName":         modelName,
		"modelAlias":        modelName,
		"modelType":         []string{"text_generate"},
		"displayName":       "API Key Assignable Model",
	}, http.StatusCreated, &model)

	assertAssignable := func() {
		t.Helper()
		var response struct {
			Items []struct {
				ID        string `json:"id"`
				ModelName string `json:"modelName"`
			} `json:"items"`
		}
		doJSON(t, server.URL, http.MethodGet, "/api/v1/api-keys/assignable-models", loginResponse.AccessToken, nil, http.StatusOK, &response)
		if !modelListContains(response.Items, model.ID) {
			t.Fatalf("user-owned model should remain assignable regardless of API key rules: %+v", response.Items)
		}
	}
	assignModel := func(apiKeyID string) {
		t.Helper()
		doJSON(t, server.URL, http.MethodPost, "/api/v1/api-keys/access-rules/batch", loginResponse.AccessToken, map[string]any{
			"subjectType": "api_key",
			"subjectId":   apiKeyID,
			"effect":      "allow",
			"upsertResources": []map[string]any{{
				"resourceType":       "platform_model",
				"resourceId":         model.ID,
				"priority":           100,
				"minPermissionLevel": 0,
				"status":             "active",
			}},
			"deleteResources": []map[string]any{},
		}, http.StatusOK, nil)
	}

	assertAssignable()
	assignModel(firstAPIKeyID)
	assertAssignable()
	assignModel(secondAPIKeyID)
	assertAssignable()
}
