package directory

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"
	"sync/atomic"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

// directoryServer serves one canned ListUsers response, recording the request
// it was asked for.
func directoryServer(t *testing.T, body string) (*httptest.Server, *atomic.Int32, *url.URL) {
	t.Helper()
	var calls atomic.Int32
	var lastURL url.URL
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		calls.Add(1)
		lastURL = *r.URL
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprint(w, body)
	}))
	t.Cleanup(srv.Close)
	return srv, &calls, &lastURL
}

func newTestClient(t *testing.T, baseURL string) *Client {
	t.Helper()
	c, err := New(Config{BaseURL: baseURL}, nil)
	require.NoError(t, err)
	return c
}

func TestSearchReturnsDirectoryEntries(t *testing.T) {
	srv, calls, gotURL := directoryServer(t, `{
		"users": [
			{
				"email": "wang.kang@pingcap.com",
				"larkUserName": "王康",
				"larkUserEnName": "Wang Kang",
				"larkDepartmentName": "Cloud"
			},
			{"email": " wei.liu@pingcap.com "}
		],
		"total": "2"
	}`)
	c := newTestClient(t, srv.URL)

	got, err := c.Search(context.Background(), " wang ", 8)
	require.NoError(t, err)
	require.Equal(t, []User{
		{Email: "wang.kang@pingcap.com", Name: "王康", EnName: "Wang Kang", Department: "Cloud"},
		{Email: "wei.liu@pingcap.com"},
	}, got)
	require.Equal(t, int32(1), calls.Load())
	require.Equal(t, "/api/v1/core/user/users", gotURL.Path)
	require.Equal(t, "wang", gotURL.Query().Get("userKeyword"))
	require.Equal(t, "8", gotURL.Query().Get("perPage"))
	require.Equal(t, "1", gotURL.Query().Get("page"))
}

func TestSearchSkipsEntriesWithoutAnEmail(t *testing.T) {
	srv, _, _ := directoryServer(t, `{"users": [{"larkUserName": "无邮箱"}, {"email": "ok@pingcap.com"}]}`)
	c := newTestClient(t, srv.URL)

	got, err := c.Search(context.Background(), "o", 8)
	require.NoError(t, err)
	require.Equal(t, []User{{Email: "ok@pingcap.com"}}, got)
}

func TestSearchCachesByKeyword(t *testing.T) {
	srv, calls, _ := directoryServer(t, `{"users": [{"email": "wang.kang@pingcap.com"}]}`)
	c := newTestClient(t, srv.URL)

	for _, keyword := range []string{"wang", "WANG", " wang "} {
		got, err := c.Search(context.Background(), keyword, 8)
		require.NoError(t, err)
		require.Len(t, got, 1)
	}
	require.Equal(t, int32(1), calls.Load(), "one keyword must not cost one request per lookup")

	_, err := c.Search(context.Background(), "wei", 8)
	require.NoError(t, err)
	require.Equal(t, int32(2), calls.Load(), "a different keyword is a different answer")
}

func TestSearchRefetchesAfterCacheExpiry(t *testing.T) {
	srv, calls, _ := directoryServer(t, `{"users": [{"email": "wang.kang@pingcap.com"}]}`)
	c, err := New(Config{BaseURL: srv.URL, CacheTTL: time.Nanosecond}, nil)
	require.NoError(t, err)

	_, err = c.Search(context.Background(), "wang", 8)
	require.NoError(t, err)
	time.Sleep(time.Millisecond)
	_, err = c.Search(context.Background(), "wang", 8)
	require.NoError(t, err)
	require.Equal(t, int32(2), calls.Load())
}

func TestSearchReportsServerErrors(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Error(w, `{"message":"boom"}`, http.StatusInternalServerError)
	}))
	t.Cleanup(srv.Close)
	c := newTestClient(t, srv.URL)

	_, err := c.Search(context.Background(), "wang", 8)
	require.ErrorContains(t, err, "HTTP 500")
	require.ErrorContains(t, err, "boom")
}

func TestSearchWithoutConfiguredDirectory(t *testing.T) {
	c, err := New(Config{}, nil)
	require.NoError(t, err)
	require.False(t, c.Enabled())

	_, err = c.Search(context.Background(), "wang", 8)
	require.ErrorIs(t, err, ErrDisabled)
}

func TestSearchRejectsAnEmptyKeyword(t *testing.T) {
	srv, calls, _ := directoryServer(t, `{"users": []}`)
	c := newTestClient(t, srv.URL)

	_, err := c.Search(context.Background(), "   ", 8)
	require.Error(t, err)
	require.Zero(t, calls.Load(), "an empty keyword must not reach core-server")
}

func TestNewRejectsAnInvalidBaseURL(t *testing.T) {
	_, err := New(Config{BaseURL: "core-server:8080"}, nil)
	require.ErrorContains(t, err, "invalid directory base_url")
}
