package server

import (
	"bytes"
	"context"
	"encoding/json"
	"io"
	"log/slog"
	"net/http"
	"net/http/httptest"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/api/auth"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/archive"
	"github.com/tidbcloud/AgentTeam/internal/config"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/coredb/testkit"
	"github.com/tidbcloud/AgentTeam/internal/mailer"
	"github.com/tidbcloud/AgentTeam/internal/router"
	"github.com/tidbcloud/AgentTeam/internal/sms"
	"github.com/tidbcloud/AgentTeam/internal/store"
	"github.com/tidbcloud/AgentTeam/internal/workspace/owner"
)

func TestCentralAuthRegistrationStartsWithoutMeshXRuntimeDependencies(t *testing.T) {
	gin.SetMode(gin.TestMode)

	var maasRequests guardedStrings
	maasServer := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		if strings.Contains(request.URL.Path, "/hosted-entitlements/grants/") ||
			request.URL.Path == "/internal/maas/v1/billing/credit-grants" {
			maasRequests.append(request.Method + " " + request.URL.Path)
		}
		writer.Header().Set("Content-Type", "application/json")
		_, _ = writer.Write([]byte(`{
			"object": "maas.hosted_entitlement_grant",
			"id": "ohg_test",
			"organizationId": "org_test",
			"hostedModelId": "glm_5_1",
			"grantSource": "agentteam_eligible_org_sync",
			"status": "active",
			"createdAt": "2026-06-15T00:00:00Z",
			"updatedAt": "2026-06-15T00:00:00Z"
		}`))
	}))
	t.Cleanup(maasServer.Close)

	ctx := context.Background()
	dsn := testkit.StartInitializedTiDBWithRuntime(t, testkit.TiDBTestRuntimeUnistore)
	sqlDB := testkit.OpenTiDB(t, dsn)
	sqlDB.SetMaxOpenConns(4)
	sqlDB.SetMaxIdleConns(4)
	t.Cleanup(func() { require.NoError(t, sqlDB.Close()) })
	db := coredb.WrapSQLDB(sqlDB)
	adminDB, ok := db.(coredb.AdminDB)
	require.True(t, ok)

	archiveStorage, err := archive.NewFilesystem(t.TempDir())
	require.NoError(t, err)
	logger := slog.New(slog.NewTextHandler(io.Discard, nil))
	stores := store.NewWithOptions(db, archiveStorage, nil, 30, store.Options{
		AppCredentialStorageKey: []byte("0123456789abcdef0123456789abcdef"),
		Logger:                  logger,
	})

	deps, side, err := wireRouterDeps(ctx, config.Config{
		PublicBaseURL: "http://agentteam.test",
		Billing:       config.BillingConfig{Enabled: true},
		MeshX:         config.MeshXConfig{Enabled: true},
		LLM: config.LLMConfig{
			MaaSBaseURL:             maasServer.URL,
			MaaSInternalBearerToken: "test-token",
		},
		Auth: config.AuthConfig{
			VerificationTokenTTL: 24 * time.Hour,
			SessionTTL:           30 * 24 * time.Hour,
			RegistrationMode:     auth.RegistrationModeOpen,
		},
		Messages: config.MessagesConfig{HotRetentionDays: 30},
	}, logger, &infra{
		coreDBConn:               adminDB,
		coreDataDB:               db,
		stores:                   stores,
		feishuCredentialKey:      []byte("0123456789abcdef0123456789abcdef"),
		mailService:              mailer.NewNoop(logger, "http://agentteam.test"),
		mobileVerificationSender: sms.NewNoop(logger),
		registrationMode:         auth.RegistrationModeOpen,
	}, nil, router.ProfileCentral, owner.NewEmptySet(), nil, nil)
	require.NoError(t, err)
	for _, closeFn := range side.closers {
		closeFn()
	}

	router := gin.New()
	auth.RegisterRoutes(router, deps.API.Auth)
	apiServer := httptest.NewServer(router)
	t.Cleanup(apiServer.Close)

	var authResponse dto.AuthTokensResponse
	response := doJSON(t, apiServer.Client(), http.MethodPost, apiServer.URL+"/api/v1/auth/register", map[string]any{
		"email":    "new-user@example.com",
		"password": "password123",
		"name":     "New User",
	}, nil, &authResponse)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.NotEmpty(t, authResponse.AccessToken)
	require.Empty(t, maasRequests.snapshot())
}

type guardedStrings struct {
	mu     sync.Mutex
	values []string
}

func (items *guardedStrings) append(value string) {
	items.mu.Lock()
	defer items.mu.Unlock()
	items.values = append(items.values, value)
}

func (items *guardedStrings) snapshot() []string {
	items.mu.Lock()
	defer items.mu.Unlock()
	return append([]string(nil), items.values...)
}

func doJSON(t *testing.T, client *http.Client, method string, url string, payload any, headers map[string]string, out any) *http.Response {
	t.Helper()

	var body io.Reader
	if payload != nil {
		encoded, err := json.Marshal(payload)
		require.NoError(t, err)
		body = bytes.NewReader(encoded)
	}
	request, err := http.NewRequestWithContext(context.Background(), method, url, body)
	require.NoError(t, err)
	if payload != nil {
		request.Header.Set("Content-Type", "application/json")
	}
	for key, value := range headers {
		request.Header.Set(key, value)
	}
	response, err := client.Do(request)
	require.NoError(t, err)
	t.Cleanup(func() { _ = response.Body.Close() })
	if out != nil {
		data, err := io.ReadAll(response.Body)
		require.NoError(t, err)
		require.NoError(t, json.Unmarshal(data, out), "response body: %s", string(data))
	}
	return response
}
