package aifin

import (
	"context"
	"encoding/json"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"sync"
	"sync/atomic"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/extension/runner"
)

type windProbeWireDouble struct {
	t          *testing.T
	credential string
	rejectPath string

	mu      sync.Mutex
	methods map[string][]string
}

func (server *windProbeWireDouble) RoundTrip(
	request *http.Request,
) (*http.Response, error) {
	server.t.Helper()
	require.Equal(server.t, "https", request.URL.Scheme)
	require.Equal(server.t, "mcp.wind.com.cn", request.URL.Host)
	require.Contains(server.t, usedEndpointPaths(), request.URL.Path)
	require.Equal(
		server.t,
		"Bearer "+server.credential,
		request.Header.Get("Authorization"),
	)

	path := request.URL.Path
	recorder := httptest.NewRecorder()
	if request.Method == http.MethodDelete {
		require.Equal(
			server.t,
			probeSessionID(path),
			request.Header.Get("Mcp-Session-Id"),
		)
		server.record(path, "DELETE")
		recorder.WriteHeader(http.StatusNoContent)
		return recorder.Result(), nil
	}
	require.Equal(server.t, http.MethodPost, request.Method)
	body, err := io.ReadAll(request.Body)
	require.NoError(server.t, err)
	var rpc windRPCRequest
	require.NoError(server.t, json.Unmarshal(body, &rpc))
	server.record(path, rpc.Method)

	switch rpc.Method {
	case "server/discover":
		writeWindRPCError(
			server.t,
			recorder,
			rpc.ID,
			-32601,
			"legacy only",
		)
	case "initialize":
		if path == server.rejectPath {
			recorder.WriteHeader(http.StatusUnauthorized)
			return recorder.Result(), nil
		}
		recorder.Header().Set("Mcp-Session-Id", probeSessionID(path))
		writeWindRPCResult(server.t, recorder, rpc.ID, map[string]any{
			"protocolVersion": "2025-06-18",
			"capabilities":    map[string]any{},
			"serverInfo": map[string]any{
				"name":    "wind-probe-double",
				"version": "1.0.0",
			},
		}, false)
	case "notifications/initialized":
		require.Equal(
			server.t,
			probeSessionID(path),
			request.Header.Get("Mcp-Session-Id"),
		)
		recorder.WriteHeader(http.StatusAccepted)
	default:
		require.Failf(
			server.t,
			"probe issued a capability operation",
			"path=%q method=%q",
			path,
			rpc.Method,
		)
		recorder.WriteHeader(http.StatusBadRequest)
	}
	return recorder.Result(), nil
}

func (server *windProbeWireDouble) record(path, method string) {
	server.mu.Lock()
	defer server.mu.Unlock()
	if server.methods == nil {
		server.methods = make(map[string][]string)
	}
	server.methods[path] = append(server.methods[path], method)
}

func (server *windProbeWireDouble) recorded(path string) []string {
	server.mu.Lock()
	defer server.mu.Unlock()
	return append([]string(nil), server.methods[path]...)
}

func TestValidateCredentialsProbesAllFiveEndpointsWithoutOperations(
	t *testing.T,
) {
	t.Parallel()

	server := &windProbeWireDouble{
		t:          t,
		credential: "wind-probe-secret",
	}
	clients, err := newProbeClients(server)
	require.NoError(t, err)
	require.NoError(t, validateCredentials(
		context.Background(),
		clients,
		runner.Credentials{"apiKey": "wind-probe-secret"},
	))

	for _, path := range usedEndpointPaths() {
		require.Equal(t, []string{
			"server/discover",
			"initialize",
			"notifications/initialized",
			"DELETE",
		}, server.recorded(path))
	}
}

func TestValidateCredentialsAttemptsAllFiveWhenOneRejects(t *testing.T) {
	t.Parallel()

	server := &windProbeWireDouble{
		t:          t,
		credential: "wind-probe-secret",
		rejectPath: "/vserver_fund_data/mcp/",
	}
	clients, err := newProbeClients(server)
	require.NoError(t, err)
	err = validateCredentials(
		context.Background(),
		clients,
		runner.Credentials{"apiKey": "wind-probe-secret"},
	)
	require.Error(t, err)
	require.Equal(
		t,
		runner.ErrorProviderAuthUnavailable,
		runner.CodeOf(err),
	)
	for _, path := range usedEndpointPaths() {
		require.Contains(t, server.recorded(path), "initialize")
	}
}

func TestProbeOnlyClientsCarryNoToolOrResourceAuthority(t *testing.T) {
	t.Parallel()

	var calls atomic.Int64
	clients, err := newProbeClients(roundTripperFunc(func(
		*http.Request,
	) (*http.Response, error) {
		calls.Add(1)
		return nil, context.Canceled
	}))
	require.NoError(t, err)
	client := clients[FamilyStockData]
	credentials := runner.Credentials{"apiKey": "wind-probe-secret"}

	_, err = client.CallTool(
		context.Background(),
		credentials,
		"get_stock_price_indicators",
		map[string]any{"windcode": "600519.SH"},
	)
	require.Error(t, err)
	require.Equal(t, runner.ErrorValidation, runner.CodeOf(err))

	_, err = client.ReadResource(
		context.Background(),
		credentials,
		"wind://anything",
	)
	require.Error(t, err)
	require.Equal(t, runner.ErrorValidation, runner.CodeOf(err))
	require.Zero(t, calls.Load())
}

func usedEndpointPaths() []string {
	paths := make([]string, 0, len(endpointPolicies))
	for _, policy := range endpointPolicies {
		path := strings.TrimPrefix(
			policy.Endpoint,
			"https://mcp.wind.com.cn",
		)
		paths = append(paths, path)
	}
	return paths
}

func probeSessionID(path string) string {
	return "probe-" + strings.NewReplacer("/", "-", "_", "-").Replace(path)
}

type roundTripperFunc func(*http.Request) (*http.Response, error)

func (roundTrip roundTripperFunc) RoundTrip(
	request *http.Request,
) (*http.Response, error) {
	return roundTrip(request)
}
