package deps

import (
	"context"
	"io"
	"net"
	"os"
	"path/filepath"
	"testing"

	"github.com/stretchr/testify/require"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/test/bufconn"

	apiproxyapi "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/api/agent-sidecar/apiproxy/v1"
	secretstoreapi "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/api/agent-sidecar/secretstore/v1"
	"github.com/tidbcloud/aisupport/packages/sdk-go/infra/rpcserver"
	sdkservice "github.com/tidbcloud/aisupport/packages/sdk-go/infra/service"
	"github.com/tidbcloud/aisupport/services/agent-sidecar/config"
	"github.com/tidbcloud/aisupport/services/agent-sidecar/internal/app"
	proxyapi "github.com/tidbcloud/aisupport/services/agent-sidecar/internal/services/apiproxy/api"
	projectapi "github.com/tidbcloud/aisupport/services/agent-sidecar/internal/services/project/api"
	secretapi "github.com/tidbcloud/aisupport/services/agent-sidecar/internal/services/secretstore/api"
)

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

	t.Run("resolve runtime dependencies", func(t *testing.T) {
		t.Parallel()

		dependencies := newTestDeps(t, false)
		var service projectapi.Service
		var secretStore secretapi.Store
		var proxyService proxyapi.Service

		err := dependencies.Container.Invoke(func(_ *sdkservice.Manager, _ *rpcserver.RPCServer, resolvedService projectapi.Service, resolvedStore secretapi.Store, resolvedProxy proxyapi.Service) {
			service = resolvedService
			secretStore = resolvedStore
			proxyService = resolvedProxy
		})

		require.NoError(t, err)
		require.Empty(t, secretStore.ListSecrets(context.Background()))
		require.Empty(t, proxyService.ListRoutes(context.Background()))
		require.NoError(t, service.(io.Closer).Close())
	})

	t.Run("return project construction failure", func(t *testing.T) {
		t.Parallel()

		dependencies := newTestDeps(t, true)

		err := dependencies.Container.Invoke(func(_ *sdkservice.Manager, _ *rpcserver.RPCServer) {})

		require.ErrorContains(t, err, "ensure global skills directory")
	})
}

func newTestDeps(t *testing.T, invalidSkills bool) *Deps {
	t.Helper()
	globalDir := t.TempDir()
	if invalidSkills {
		require.NoError(t, os.WriteFile(filepath.Join(globalDir, "skills"), nil, 0o644))
	}
	binary, err := os.Executable()
	require.NoError(t, err)
	conf := config.Default()
	conf.Services.Project.ProjectsRootDir = t.TempDir()
	conf.Services.Project.CodexHomeDir = globalDir
	conf.Services.Project.GitBinary = binary
	conf.Normalize()
	require.NoError(t, conf.Validate())
	dependencies := NewDeps()
	require.NoError(t, dependencies.Init(context.Background(), &conf))
	return dependencies
}

func TestResourceRPCHandlersAreRegistered(t *testing.T) {
	t.Parallel()
	dependencies := newTestDeps(t, false)
	server := grpc.NewServer()
	require.NoError(t, dependencies.Container.Invoke(func(param app.RPCServerParam, projects projectapi.Service) {
		t.Cleanup(func() { require.NoError(t, projects.(io.Closer).Close()) })
		for _, handler := range param.Handlers {
			require.NoError(t, handler.RegisterRPC(server))
		}
	}))
	listener := bufconn.Listen(1 << 20)
	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() })
	routes, err := apiproxyapi.NewAPIProxyServiceClient(conn).ListRoutes(context.Background(), &apiproxyapi.ListRoutesRequest{})
	require.NoError(t, err)
	require.Empty(t, routes.GetRoutes())
	secrets, err := secretstoreapi.NewSecretStoreServiceClient(conn).ListSecrets(context.Background(), &secretstoreapi.ListSecretsRequest{})
	require.NoError(t, err)
	require.Empty(t, secrets.GetSecrets())
}
