package agent

import (
	"context"
	"net"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"github.com/stretchr/testify/require"
	"go.uber.org/mock/gomock"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/test/bufconn"
	"google.golang.org/protobuf/encoding/protojson"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/types/known/durationpb"
	"google.golang.org/protobuf/types/known/timestamppb"

	agentapi "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/api/agent-gateway/agent/v1"
	apiproxyv1 "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/entity/agent-sidecar/apiproxy/v1"
	secretstorev1 "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/entity/agent-sidecar/secretstore/v1"
	serviceapi "github.com/tidbcloud/aisupport/services/agent-gateway/internal/services/agent/api"
)

func TestResourceListsOverHTTPAndRPC(t *testing.T) {
	t.Parallel()
	service := serviceapi.NewMockManagement(gomock.NewController(t))
	handler := &Handler{service: service}
	listener := bufconn.Listen(1 << 20)
	server := grpc.NewServer()
	require.NoError(t, handler.RegisterRPC(server))
	go func() { _ = server.Serve(listener) }()
	t.Cleanup(func() { server.Stop(); _ = listener.Close() })
	conn, err := grpc.NewClient("passthrough:///sidecar", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return listener.Dial() }))
	require.NoError(t, err)
	t.Cleanup(func() { _ = conn.Close() })
	mux := runtime.NewServeMux()
	require.NoError(t, handler.RegisterGateway(context.Background(), mux, conn))

	credential := &apiproxyv1.CredentialAuthConfig{Candidates: []*apiproxyv1.AuthCandidate{{Type: apiproxyv1.AuthType_AUTH_TYPE_BEARER, ValueFrom: &apiproxyv1.SecretRef{Secret: "credential", Field: "token"}}}}
	routes := &agentapi.ListAPIProxyRoutesResponse{Routes: []*apiproxyv1.Route{{RouteId: "devops", ListenerId: "default", Prefix: "/devops", Upstream: &apiproxyv1.Upstream{BaseUrl: "https://upstream.example"}, Middlewares: []*apiproxyv1.Middleware{
		{Config: &apiproxyv1.Middleware_OperationPolicy{OperationPolicy: &apiproxyv1.OperationPolicyConfig{Allow: []*apiproxyv1.Operation{{Method: http.MethodGet, PathMatcher: &apiproxyv1.Operation_PathPrefix{PathPrefix: "/api/"}}}}}},
		{Config: &apiproxyv1.Middleware_CredentialAuth{CredentialAuth: credential}},
		{Config: &apiproxyv1.Middleware_LarkAuthProxy{LarkAuthProxy: &apiproxyv1.LarkAuthProxyConfig{KeyFrom: &apiproxyv1.SecretRef{Secret: "signing", Field: "key"}}}},
	}, AllowedRequestHeaders: []string{"Accept"}}}}
	service.EXPECT().ListAPIProxyRoutes(gomock.Any(), gomock.Cond(func(req *agentapi.ListAPIProxyRoutesRequest) bool { return req.GetAgentId() == "agent-a" })).Return(routes, nil)
	request := httptest.NewRequest(http.MethodGet, "/api/v1/agent-gateway/agents/agent-a/routes", nil)
	recorder := httptest.NewRecorder()
	mux.ServeHTTP(recorder, request)
	require.Equal(t, http.StatusOK, recorder.Code, recorder.Body.String())
	require.NotContains(t, recorder.Body.String(), "@type")
	decodedRoutes := &agentapi.ListAPIProxyRoutesResponse{}
	require.NoError(t, protojson.Unmarshal(recorder.Body.Bytes(), decodedRoutes))
	require.True(t, proto.Equal(routes, decodedRoutes))

	for _, source := range []*secretstorev1.SecretSource{
		{Config: &secretstorev1.SecretSource_AwsSecretsManager{AwsSecretsManager: &secretstorev1.AWSSecretsManagerSourceConfig{Region: "us-west-2", SecretId: "secret-id", Format: secretstorev1.SecretFormat_SECRET_FORMAT_JSON}}},
		{Config: &secretstorev1.SecretSource_O11YExchange{O11YExchange: &secretstorev1.O11YExchangeSourceConfig{Bootstrap: &secretstorev1.SecretRef{Name: "bootstrap", Field: "token"}, TokenTtl: durationpb.New(time.Hour)}}},
		{Config: &secretstorev1.SecretSource_LarkTenantToken{LarkTenantToken: &secretstorev1.LarkTenantTokenSourceConfig{AppId: "app-id", AppSecret: &secretstorev1.SecretRef{Name: "app-secret", Field: "value"}}}},
	} {
		secrets := &agentapi.ListSecretStoreSecretsResponse{Secrets: []*secretstorev1.Secret{{Name: "credential", Source: source, Refresh: &secretstorev1.SecretRefresh{Interval: durationpb.New(time.Minute)}, Status: &secretstorev1.SecretStatus{State: secretstorev1.SecretState_SECRET_STATE_STALE, Version: "v1", LastError: "request timed out", LastAttemptAt: timestamppb.New(time.Unix(1700000000, 0))}}}}
		service.EXPECT().ListSecretStoreSecrets(gomock.Any(), gomock.Cond(func(req *agentapi.ListSecretStoreSecretsRequest) bool { return req.GetAgentId() == "agent-b" })).Return(secrets, nil)
		request := httptest.NewRequest(http.MethodGet, "/api/v1/agent-gateway/agents/agent-b/secrets", nil)
		recorder := httptest.NewRecorder()
		mux.ServeHTTP(recorder, request)
		require.Equal(t, http.StatusOK, recorder.Code, recorder.Body.String())
		require.NotContains(t, recorder.Body.String(), "@type")
		decodedSecrets := &agentapi.ListSecretStoreSecretsResponse{}
		require.NoError(t, protojson.Unmarshal(recorder.Body.Bytes(), decodedSecrets))
		require.True(t, proto.Equal(secrets, decodedSecrets))
	}
}
