package coredeno

import (
	"context"
	"os"
	"path/filepath"
	"testing"
	"time"

	pb "forge.lthn.ai/core/go/pkg/coredeno/proto"
	core "forge.lthn.ai/core/go/pkg/framework/core"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func TestNewServiceFactory_Good(t *testing.T) {
	opts := Options{
		DenoPath:   "echo",
		SocketPath: "/tmp/test-service.sock",
	}
	c, err := core.New()
	require.NoError(t, err)

	factory := NewServiceFactory(opts)
	result, err := factory(c)
	require.NoError(t, err)

	svc, ok := result.(*Service)
	require.True(t, ok)
	assert.NotNil(t, svc.sidecar)
	assert.Equal(t, "echo", svc.sidecar.opts.DenoPath)
	assert.NotNil(t, svc.Core(), "ServiceRuntime should provide Core access")
	assert.Equal(t, opts, svc.Opts(), "ServiceRuntime should provide Options access")
}

func TestService_WithService_Good(t *testing.T) {
	opts := Options{DenoPath: "echo"}
	c, err := core.New(core.WithService(NewServiceFactory(opts)))
	require.NoError(t, err)
	assert.NotNil(t, c)
}

func TestService_Lifecycle_Good(t *testing.T) {
	tmpDir := t.TempDir()
	sockPath := filepath.Join(tmpDir, "lifecycle.sock")

	c, err := core.New()
	require.NoError(t, err)

	factory := NewServiceFactory(Options{
		DenoPath:   "echo",
		SocketPath: sockPath,
	})
	result, _ := factory(c)
	svc := result.(*Service)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// Verify Startable
	err = svc.OnStartup(ctx)
	assert.NoError(t, err)

	// Verify Stoppable
	err = svc.OnShutdown(context.Background())
	assert.NoError(t, err)
}

func TestService_Sidecar_Good(t *testing.T) {
	c, err := core.New()
	require.NoError(t, err)

	factory := NewServiceFactory(Options{DenoPath: "echo"})
	result, _ := factory(c)
	svc := result.(*Service)

	assert.NotNil(t, svc.Sidecar())
}

func TestService_OnStartup_Good(t *testing.T) {
	tmpDir := t.TempDir()
	sockPath := filepath.Join(tmpDir, "core.sock")

	// Write a minimal manifest
	coreDir := filepath.Join(tmpDir, ".core")
	require.NoError(t, os.MkdirAll(coreDir, 0755))
	require.NoError(t, os.WriteFile(filepath.Join(coreDir, "view.yml"), []byte(`
code: test-app
name: Test App
version: "1.0"
permissions:
  read: ["./data/"]
  write: ["./data/"]
`), 0644))

	opts := Options{
		DenoPath:    "sleep",
		SocketPath:  sockPath,
		AppRoot:     tmpDir,
		StoreDBPath: ":memory:",
		SidecarArgs: []string{"60"},
	}

	c, err := core.New()
	require.NoError(t, err)

	factory := NewServiceFactory(opts)
	result, err := factory(c)
	require.NoError(t, err)
	svc := result.(*Service)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	err = svc.OnStartup(ctx)
	require.NoError(t, err)

	// Verify socket appeared
	require.Eventually(t, func() bool {
		_, err := os.Stat(sockPath)
		return err == nil
	}, 2*time.Second, 10*time.Millisecond, "gRPC socket should appear after startup")

	// Verify gRPC responds
	conn, err := grpc.NewClient(
		"unix://"+sockPath,
		grpc.WithTransportCredentials(insecure.NewCredentials()),
	)
	require.NoError(t, err)
	defer conn.Close()

	client := pb.NewCoreServiceClient(conn)
	_, err = client.StoreSet(ctx, &pb.StoreSetRequest{
		Group: "boot", Key: "ok", Value: "true",
	})
	require.NoError(t, err)

	resp, err := client.StoreGet(ctx, &pb.StoreGetRequest{
		Group: "boot", Key: "ok",
	})
	require.NoError(t, err)
	assert.True(t, resp.Found)
	assert.Equal(t, "true", resp.Value)

	// Verify sidecar is running
	assert.True(t, svc.sidecar.IsRunning(), "sidecar should be running")

	// Shutdown
	err = svc.OnShutdown(context.Background())
	assert.NoError(t, err)
	assert.False(t, svc.sidecar.IsRunning(), "sidecar should be stopped")
}

func TestService_OnStartup_Good_NoManifest(t *testing.T) {
	tmpDir := t.TempDir()
	sockPath := filepath.Join(tmpDir, "core.sock")

	opts := Options{
		DenoPath:    "sleep",
		SocketPath:  sockPath,
		AppRoot:     tmpDir,
		StoreDBPath: ":memory:",
	}

	c, err := core.New()
	require.NoError(t, err)

	factory := NewServiceFactory(opts)
	result, _ := factory(c)
	svc := result.(*Service)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// Should succeed even without .core/view.yml
	err = svc.OnStartup(ctx)
	require.NoError(t, err)

	err = svc.OnShutdown(context.Background())
	assert.NoError(t, err)
}
