package coredeno

import (
	"context"
	"fmt"
	"testing"

	pb "forge.lthn.ai/core/go/pkg/coredeno/proto"
	"forge.lthn.ai/core/go/pkg/io"
	"forge.lthn.ai/core/go/pkg/manifest"
	"forge.lthn.ai/core/go/pkg/store"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

// mockProcessRunner implements ProcessRunner for testing.
type mockProcessRunner struct {
	started map[string]bool
	nextID  int
}

func newMockProcessRunner() *mockProcessRunner {
	return &mockProcessRunner{started: make(map[string]bool)}
}

func (m *mockProcessRunner) Start(_ context.Context, command string, args ...string) (ProcessHandle, error) {
	m.nextID++
	id := fmt.Sprintf("proc-%d", m.nextID)
	m.started[id] = true
	return &mockProcessHandle{id: id}, nil
}

func (m *mockProcessRunner) Kill(id string) error {
	if !m.started[id] {
		return fmt.Errorf("process not found: %s", id)
	}
	delete(m.started, id)
	return nil
}

type mockProcessHandle struct{ id string }

func (h *mockProcessHandle) Info() ProcessInfo { return ProcessInfo{ID: h.id} }

func newTestServer(t *testing.T) *Server {
	t.Helper()
	medium := io.NewMockMedium()
	medium.Files["./data/test.txt"] = "hello"
	st, err := store.New(":memory:")
	require.NoError(t, err)
	t.Cleanup(func() { st.Close() })

	srv := NewServer(medium, st)
	srv.RegisterModule(&manifest.Manifest{
		Code: "test-mod",
		Permissions: manifest.Permissions{
			Read:  []string{"./data/"},
			Write: []string{"./data/"},
		},
	})
	return srv
}

func TestFileRead_Good(t *testing.T) {
	srv := newTestServer(t)
	resp, err := srv.FileRead(context.Background(), &pb.FileReadRequest{
		Path: "./data/test.txt", ModuleCode: "test-mod",
	})
	require.NoError(t, err)
	assert.Equal(t, "hello", resp.Content)
}

func TestFileRead_Bad_PermissionDenied(t *testing.T) {
	srv := newTestServer(t)
	_, err := srv.FileRead(context.Background(), &pb.FileReadRequest{
		Path: "./secrets/key.pem", ModuleCode: "test-mod",
	})
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "permission denied")
}

func TestFileRead_Bad_UnknownModule(t *testing.T) {
	srv := newTestServer(t)
	_, err := srv.FileRead(context.Background(), &pb.FileReadRequest{
		Path: "./data/test.txt", ModuleCode: "unknown",
	})
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "unknown module")
}

func TestFileWrite_Good(t *testing.T) {
	srv := newTestServer(t)
	resp, err := srv.FileWrite(context.Background(), &pb.FileWriteRequest{
		Path: "./data/new.txt", Content: "world", ModuleCode: "test-mod",
	})
	require.NoError(t, err)
	assert.True(t, resp.Ok)
}

func TestFileWrite_Bad_PermissionDenied(t *testing.T) {
	srv := newTestServer(t)
	_, err := srv.FileWrite(context.Background(), &pb.FileWriteRequest{
		Path: "./secrets/bad.txt", Content: "nope", ModuleCode: "test-mod",
	})
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "permission denied")
}

func TestStoreGetSet_Good(t *testing.T) {
	srv := newTestServer(t)
	ctx := context.Background()

	_, err := srv.StoreSet(ctx, &pb.StoreSetRequest{Group: "cfg", Key: "theme", Value: "dark"})
	require.NoError(t, err)

	resp, err := srv.StoreGet(ctx, &pb.StoreGetRequest{Group: "cfg", Key: "theme"})
	require.NoError(t, err)
	assert.True(t, resp.Found)
	assert.Equal(t, "dark", resp.Value)
}

func TestStoreGet_Good_NotFound(t *testing.T) {
	srv := newTestServer(t)
	resp, err := srv.StoreGet(context.Background(), &pb.StoreGetRequest{Group: "cfg", Key: "missing"})
	require.NoError(t, err)
	assert.False(t, resp.Found)
}

func newTestServerWithProcess(t *testing.T) (*Server, *mockProcessRunner) {
	t.Helper()
	srv := newTestServer(t)
	srv.RegisterModule(&manifest.Manifest{
		Code: "runner-mod",
		Permissions: manifest.Permissions{
			Run: []string{"echo", "ls"},
		},
	})
	pr := newMockProcessRunner()
	srv.SetProcessRunner(pr)
	return srv, pr
}

func TestProcessStart_Good(t *testing.T) {
	srv, _ := newTestServerWithProcess(t)
	resp, err := srv.ProcessStart(context.Background(), &pb.ProcessStartRequest{
		Command: "echo", Args: []string{"hello"}, ModuleCode: "runner-mod",
	})
	require.NoError(t, err)
	assert.NotEmpty(t, resp.ProcessId)
}

func TestProcessStart_Bad_PermissionDenied(t *testing.T) {
	srv, _ := newTestServerWithProcess(t)
	_, err := srv.ProcessStart(context.Background(), &pb.ProcessStartRequest{
		Command: "rm", Args: []string{"-rf", "/"}, ModuleCode: "runner-mod",
	})
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "permission denied")
}

func TestProcessStart_Bad_NoProcessService(t *testing.T) {
	srv := newTestServer(t)
	srv.RegisterModule(&manifest.Manifest{
		Code: "no-proc-mod",
		Permissions: manifest.Permissions{Run: []string{"echo"}},
	})
	_, err := srv.ProcessStart(context.Background(), &pb.ProcessStartRequest{
		Command: "echo", ModuleCode: "no-proc-mod",
	})
	assert.Error(t, err)
	st, ok := status.FromError(err)
	require.True(t, ok)
	assert.Equal(t, codes.Unimplemented, st.Code())
}

func TestProcessStop_Good(t *testing.T) {
	srv, _ := newTestServerWithProcess(t)
	// Start a process first
	startResp, err := srv.ProcessStart(context.Background(), &pb.ProcessStartRequest{
		Command: "echo", ModuleCode: "runner-mod",
	})
	require.NoError(t, err)

	// Stop it
	resp, err := srv.ProcessStop(context.Background(), &pb.ProcessStopRequest{
		ProcessId: startResp.ProcessId,
	})
	require.NoError(t, err)
	assert.True(t, resp.Ok)
}

func TestProcessStop_Bad_NotFound(t *testing.T) {
	srv, _ := newTestServerWithProcess(t)
	_, err := srv.ProcessStop(context.Background(), &pb.ProcessStopRequest{
		ProcessId: "nonexistent",
	})
	assert.Error(t, err)
}
