package workbench

import (
	"bytes"
	"context"
	"encoding/base64"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"strings"
	"testing"

	"aigateway.local/core/internal/apikey"
	"aigateway.local/core/internal/gateway"
	"aigateway.local/core/internal/platform/config"
	"aigateway.local/core/internal/platform/cryptox"
	"aigateway.local/core/internal/platform/database"
)

func TestWorkbenchPostgreSQLLifecycle(t *testing.T) {
	databaseURL := os.Getenv("WORKBENCH_TEST_DATABASE_URL")
	if databaseURL == "" {
		t.Skip("WORKBENCH_TEST_DATABASE_URL is not set")
	}
	ctx := context.Background()
	pool, err := database.Open(ctx, config.Database{URL: databaseURL, MaxConns: 8, MinConns: 0})
	if err != nil {
		t.Fatal(err)
	}
	defer pool.Close()
	actorID := "11111111-1111-4111-8111-111111111111"
	_, err = pool.Exec(ctx, `INSERT INTO gateway.admin_accounts(id,username,password_hash,role) VALUES($1,'m4-test','test','superadmin') ON CONFLICT(id) DO NOTHING`, actorID)
	if err != nil {
		t.Fatal(err)
	}
	cleanup := func() {
		_, _ = pool.Exec(ctx, `DELETE FROM gateway.notification_channels WHERE name='m4-webhook'; DELETE FROM gateway.applications WHERE code='m4_app'; DELETE FROM gateway.tool_definitions WHERE code='m4_lookup'; DELETE FROM gateway.knowledge_bases WHERE name='m4-integration-kb'; DELETE FROM gateway.prompt_templates WHERE name='m4-integration-prompt'`)
	}
	cleanup()
	defer cleanup()
	assets := NewService(pool)
	prompt, err := assets.CreatePrompt(ctx, PromptInput{Name: "m4-integration-prompt", Description: "test", Enabled: true, Content: "请回答 {{question}}", Variables: []Variable{{Name: "question", Required: true}}}, actorID)
	if err != nil {
		t.Fatal(err)
	}
	if prompt.Current == nil || prompt.Current.Version != 1 {
		t.Fatalf("unexpected prompt: %#v", prompt)
	}
	version, err := assets.AddPromptVersion(ctx, prompt.ID, "新版 {{question}}", []Variable{{Name: "question", Required: true}}, "v2", actorID, true)
	if err != nil || version.Version != 2 {
		t.Fatalf("version=%#v err=%v", version, err)
	}
	_, rendered, err := assets.RenderPromptByName(ctx, prompt.Name, map[string]any{"question": "可扩展吗"})
	if err != nil || rendered != "新版 可扩展吗" {
		t.Fatalf("render=%q err=%v", rendered, err)
	}
	kb, err := assets.SaveKnowledgeBase(ctx, KnowledgeBase{Name: "m4-integration-kb", Description: "test", RetrievalMode: "postgres_fts", ChunkSize: 200, ChunkOverlap: 20, Enabled: true}, actorID, true)
	if err != nil {
		t.Fatal(err)
	}
	doc, err := assets.AddKnowledgeDocument(ctx, kb.ID, "Go 架构说明", "text", "", "Go 网关采用不可变运行时快照。\n\nPostgreSQL 是权威配置存储。", actorID)
	if err != nil {
		t.Fatal(err)
	}
	if doc.ChunkCount == 0 {
		t.Fatal("expected chunks")
	}
	hits, err := NewPostgreSQLRetriever(assets).Search(ctx, kb.ID, "不可变运行时快照", 4)
	if err != nil || len(hits) == 0 {
		t.Fatalf("hits=%#v err=%v", hits, err)
	}
	key := base64.StdEncoding.EncodeToString(make([]byte, 32))
	cipher, err := cryptox.NewKeyring(key, 1, "", "m4-test-tools")
	if err != nil {
		t.Fatal(err)
	}
	receivedAuth := ""
	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		receivedAuth = r.Header.Get("Authorization")
		_ = json.NewEncoder(w).Encode(map[string]any{"ok": true})
	}))
	defer upstream.Close()
	tools := NewToolService(assets, cipher, true)
	tool, err := tools.Save(ctx, "", ToolInput{Code: "m4_lookup", Name: "查询", EndpointURL: upstream.URL, HTTPMethod: "POST", Headers: map[string]string{"Authorization": "Bearer secret"}, InputSchema: json.RawMessage(`{"type":"object","required":["q"],"properties":{"q":{"type":"string"}}}`), TimeoutSeconds: 5, Enabled: true}, actorID, true)
	if err != nil {
		t.Fatal(err)
	}
	result, err := tools.Execute(ctx, tool, map[string]any{"q": "gateway"}, "", "m4-test")
	if err != nil {
		t.Fatal(err)
	}
	if result["status_code"] != http.StatusOK || receivedAuth != "Bearer secret" {
		t.Fatalf("result=%#v auth=%q", result, receivedAuth)
	}
	app, err := assets.SaveApplication(ctx, Application{Code: "m4_app", Name: "M4 App", Status: "draft", DraftConfig: ApplicationConfig{Model: "test-model", PromptTemplateID: prompt.ID, KnowledgeBaseIDs: []string{kb.ID}, ToolIDs: []string{tool.ID}, RetrievalTopK: 4, Temperature: .2, MaxToolRounds: 2}}, actorID, true)
	if err != nil {
		t.Fatal(err)
	}
	published, err := assets.PublishApplication(ctx, app.ID, "first", actorID)
	if err != nil {
		t.Fatal(err)
	}
	if published.PublishedVersion == nil || *published.PublishedVersion != 1 || published.Status != "active" {
		t.Fatalf("unexpected published app: %#v", published)
	}
	governed := false
	fakeGateway := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var payload map[string]any
		if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
			t.Error(err)
		}
		messages, _ := payload["messages"].([]any)
		if len(messages) > 0 && strings.Contains(toString(messages[0]), "不可变运行时快照") {
			governed = true
		}
		_ = json.NewEncoder(w).Encode(map[string]any{"choices": []any{map[string]any{"message": map[string]any{"role": "assistant", "content": "完成"}}}})
	})
	runtime := NewRuntimeHTTPHandler(assets, tools, NewPostgreSQLRetriever(assets), staticPrincipalAuthenticator{}, fakeGateway, MarketplaceDeps{})
	runtimeRequest := httptest.NewRequest(http.MethodPost, "/v1/applications/m4_app/chat/completions", bytes.NewBufferString(`{"messages":[{"role":"user","content":"不可变运行时快照是什么？"}],"variables":{"question":"架构"}}`))
	runtimeRequest.Header.Set("Authorization", "Bearer test")
	runtimeRequest = runtimeRequest.WithContext(gateway.WithRequestID(runtimeRequest.Context(), "m4-runtime"))
	runtimeResponse := httptest.NewRecorder()
	runtime.ServeHTTP(runtimeResponse, runtimeRequest)
	if runtimeResponse.Code != http.StatusOK || !governed || !strings.Contains(runtimeResponse.Body.String(), `"application"`) {
		t.Fatalf("runtime status=%d governed=%v body=%s", runtimeResponse.Code, governed, runtimeResponse.Body.String())
	}

	signed := ""
	webhook := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		signed = r.Header.Get("X-Gateway-Signature")
		w.WriteHeader(http.StatusNoContent)
	}))
	defer webhook.Close()
	notifications := NewNotificationService(assets, cipher, true)
	secret := "m4-signing-secret"
	channel, err := notifications.SaveChannel(ctx, "", NotificationInput{Name: "m4-webhook", WebhookURL: webhook.URL, SigningSecret: &secret, EventPatterns: []string{"application.*"}, Enabled: true}, actorID, true)
	if err != nil {
		t.Fatal(err)
	}
	eventID, _ := newUUID()
	delivery, err := notifications.ensureDelivery(ctx, channel, eventID, "application.published", json.RawMessage(`{"version":1}`))
	if err != nil {
		t.Fatal(err)
	}
	if err = notifications.deliver(ctx, channel, delivery); err != nil || !strings.HasPrefix(signed, "sha256=") {
		t.Fatalf("delivery err=%v signature=%q", err, signed)
	}
}

type staticPrincipalAuthenticator struct{}

func (staticPrincipalAuthenticator) AuthenticatePrincipal(context.Context, string) (apikey.Principal, error) {
	return apikey.Principal{}, nil
}
