package workspace

import (
	"context"
	"encoding/json"
	"fmt"
	"path/filepath"
	"testing"

	"forge.lthn.ai/core/gui/pkg/core"
	"forge.lthn.ai/core/gui/pkg/io"
	"github.com/stretchr/testify/assert"
	"github.com/wailsapp/wails/v3/pkg/application"
)

// mockConfig is a mock implementation of the core.Config interface for testing.
type mockConfig struct {
	values map[string]interface{}
}

func (m *mockConfig) Get(key string, out any) error {
	val, ok := m.values[key]
	if !ok {
		return fmt.Errorf("key not found: %s", key)
	}
	// This is a simplified mock; a real one would use reflection to set `out`
	switch v := out.(type) {
	case *string:
		*v = val.(string)
	default:
		return fmt.Errorf("unsupported type in mock config Get")
	}
	return nil
}

func (m *mockConfig) Set(key string, v any) error {
	m.values[key] = v
	return nil
}

// newTestService creates a workspace service instance with mocked dependencies.
func newTestService(t *testing.T, workspaceDir string) (*Service, *io.MockMedium) {
	coreInstance, err := core.New()
	assert.NoError(t, err)

	mockCfg := &mockConfig{values: map[string]interface{}{"workspaceDir": workspaceDir}}
	coreInstance.RegisterService("config", mockCfg)

	mockMedium := io.NewMockMedium()
	service, err := New(mockMedium)
	assert.NoError(t, err)

	service.ServiceRuntime = core.NewServiceRuntime(coreInstance, Options{})

	return service, mockMedium
}

func TestServiceStartup(t *testing.T) {
	workspaceDir := "/tmp/workspace"

	t.Run("existing valid list.json", func(t *testing.T) {
		service, mockMedium := newTestService(t, workspaceDir)

		expectedWorkspaceList := map[string]string{
			"workspace1": "pubkey1",
			"workspace2": "pubkey2",
		}
		listContent, _ := json.MarshalIndent(expectedWorkspaceList, "", "  ")
		listPath := filepath.Join(workspaceDir, listFile)
		mockMedium.Files[listPath] = string(listContent)

		err := service.ServiceStartup(context.Background(), application.ServiceOptions{})

		assert.NoError(t, err)
		// assert.Equal(t, expectedWorkspaceList, service.workspaceList) // This check is difficult with current implementation
		assert.NotNil(t, service.activeWorkspace)
		assert.Equal(t, defaultWorkspace, service.activeWorkspace.Name)
	})
}

func TestCreateAndSwitchWorkspace(t *testing.T) {
	workspaceDir := "/tmp/workspace"
	service, _ := newTestService(t, workspaceDir)

	// Create
	workspaceID, err := service.CreateWorkspace("test", "password")
	assert.NoError(t, err)
	assert.NotEmpty(t, workspaceID)

	// Switch
	err = service.SwitchWorkspace(workspaceID)
	assert.NoError(t, err)
	assert.Equal(t, workspaceID, service.activeWorkspace.Name)
}

func TestWorkspaceFileOperations(t *testing.T) {
	workspaceDir := "/tmp/workspace"

	t.Run("FileGet returns error when no active workspace", func(t *testing.T) {
		service, _ := newTestService(t, workspaceDir)
		// Don't call ServiceStartup so there's no active workspace

		_, err := service.WorkspaceFileGet("test.txt")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "no active workspace")
	})

	t.Run("FileSet returns error when no active workspace", func(t *testing.T) {
		service, _ := newTestService(t, workspaceDir)
		// Don't call ServiceStartup so there's no active workspace

		err := service.WorkspaceFileSet("test.txt", "content")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "no active workspace")
	})

	t.Run("FileGet and FileSet work with active workspace", func(t *testing.T) {
		service, mockMedium := newTestService(t, workspaceDir)

		// Start up the service to set active workspace
		err := service.ServiceStartup(context.Background(), application.ServiceOptions{})
		assert.NoError(t, err)

		// Test FileSet
		err = service.WorkspaceFileSet("test.txt", "hello world")
		assert.NoError(t, err)

		// Verify file was written to mock medium
		expectedPath := filepath.Join(workspaceDir, defaultWorkspace, "test.txt")
		assert.Equal(t, "hello world", mockMedium.Files[expectedPath])

		// Test FileGet
		content, err := service.WorkspaceFileGet("test.txt")
		assert.NoError(t, err)
		assert.Equal(t, "hello world", content)
	})
}

func TestListWorkspaces(t *testing.T) {
	workspaceDir := "/tmp/workspace"
	service, _ := newTestService(t, workspaceDir)

	t.Run("returns empty list when no workspaces", func(t *testing.T) {
		workspaces := service.ListWorkspaces()
		assert.Empty(t, workspaces)
	})

	t.Run("returns list after creating workspaces", func(t *testing.T) {
		// Create some workspaces
		id1, err := service.CreateWorkspace("test1", "password")
		assert.NoError(t, err)
		id2, err := service.CreateWorkspace("test2", "password")
		assert.NoError(t, err)

		workspaces := service.ListWorkspaces()
		assert.Len(t, workspaces, 2)
		assert.Contains(t, workspaces, id1)
		assert.Contains(t, workspaces, id2)
	})
}

func TestActiveWorkspace(t *testing.T) {
	workspaceDir := "/tmp/workspace"
	service, _ := newTestService(t, workspaceDir)

	t.Run("returns nil when no active workspace", func(t *testing.T) {
		workspace := service.ActiveWorkspace()
		assert.Nil(t, workspace)
	})

	t.Run("returns workspace after startup", func(t *testing.T) {
		err := service.ServiceStartup(context.Background(), application.ServiceOptions{})
		assert.NoError(t, err)

		workspace := service.ActiveWorkspace()
		assert.NotNil(t, workspace)
		assert.Equal(t, defaultWorkspace, workspace.Name)
	})
}

func TestCreateWorkspaceErrors(t *testing.T) {
	workspaceDir := "/tmp/workspace"

	t.Run("returns error for duplicate workspace", func(t *testing.T) {
		service, _ := newTestService(t, workspaceDir)

		// Create first workspace
		_, err := service.CreateWorkspace("duplicate-test", "password")
		assert.NoError(t, err)

		// Try to create duplicate
		_, err = service.CreateWorkspace("duplicate-test", "password")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "already exists")
	})
}

func TestSwitchWorkspaceErrors(t *testing.T) {
	workspaceDir := "/tmp/workspace"
	service, _ := newTestService(t, workspaceDir)

	t.Run("returns error for non-existent workspace", func(t *testing.T) {
		err := service.SwitchWorkspace("non-existent-workspace")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "does not exist")
	})

	t.Run("default workspace is always accessible", func(t *testing.T) {
		err := service.SwitchWorkspace(defaultWorkspace)
		assert.NoError(t, err)
	})
}

func TestNewWorkspaceService(t *testing.T) {
	t.Run("creates service with mock medium", func(t *testing.T) {
		mockMedium := io.NewMockMedium()
		service, err := New(mockMedium)

		assert.NoError(t, err)
		assert.NotNil(t, service)
		assert.NotNil(t, service.workspaceList)
		assert.Equal(t, mockMedium, service.medium)
	})
}

func TestServiceStartupWithInvalidJSON(t *testing.T) {
	workspaceDir := "/tmp/workspace"
	service, mockMedium := newTestService(t, workspaceDir)

	// Add invalid JSON to list.json
	listPath := filepath.Join(workspaceDir, listFile)
	mockMedium.Files[listPath] = "invalid-json{{"

	// ServiceStartup should warn but continue
	err := service.ServiceStartup(context.Background(), application.ServiceOptions{})
	assert.NoError(t, err) // Should not error, just warn

	// Workspace list should be empty/reset
	assert.NotNil(t, service.activeWorkspace)
}

func TestHandleIPCEvents(t *testing.T) {
	workspaceDir := "/tmp/workspace"

	t.Run("handles switch workspace action", func(t *testing.T) {
		coreInstance, err := core.New()
		assert.NoError(t, err)

		mockCfg := &mockConfig{values: map[string]interface{}{"workspaceDir": workspaceDir}}
		coreInstance.RegisterService("config", mockCfg)

		mockMedium := io.NewMockMedium()
		service, err := New(mockMedium)
		assert.NoError(t, err)
		service.ServiceRuntime = core.NewServiceRuntime(coreInstance, Options{})

		// First startup to initialize workspace list and create default workspace
		err = service.ServiceStartup(context.Background(), application.ServiceOptions{})
		assert.NoError(t, err)

		// Create a workspace to switch to
		wsID, err := service.CreateWorkspace("ipc-test", "password")
		assert.NoError(t, err)

		// Test IPC switch workspace action
		msg := map[string]any{
			"action": "workspace.switch_workspace",
			"name":   wsID,
		}

		err = service.HandleIPCEvents(coreInstance, msg)
		assert.NoError(t, err)
		assert.Equal(t, wsID, service.activeWorkspace.Name)
	})

	t.Run("handles ActionServiceStartup message", func(t *testing.T) {
		coreInstance, err := core.New()
		assert.NoError(t, err)

		mockCfg := &mockConfig{values: map[string]interface{}{"workspaceDir": workspaceDir}}
		coreInstance.RegisterService("config", mockCfg)

		mockMedium := io.NewMockMedium()
		service, err := New(mockMedium)
		assert.NoError(t, err)
		service.ServiceRuntime = core.NewServiceRuntime(coreInstance, Options{})

		// Send ActionServiceStartup message
		err = service.HandleIPCEvents(coreInstance, core.ActionServiceStartup{})
		assert.NoError(t, err)
		assert.NotNil(t, service.activeWorkspace)
	})

	// Skipping "logs error for unknown message type" test as it requires core.App.Logger to be initialized
	// which requires Wails runtime

	// Skipping "handles map message with non-workspace action" test as it falls through to default
	// case which requires core.App.Logger
}

func TestGetWorkspaceDirError(t *testing.T) {
	t.Run("returns error when config missing workspaceDir", func(t *testing.T) {
		coreInstance, err := core.New()
		assert.NoError(t, err)

		// Register config without workspaceDir
		mockCfg := &mockConfig{values: map[string]interface{}{}}
		coreInstance.RegisterService("config", mockCfg)

		mockMedium := io.NewMockMedium()
		service, err := New(mockMedium)
		assert.NoError(t, err)
		service.ServiceRuntime = core.NewServiceRuntime(coreInstance, Options{})

		// ServiceStartup should fail because workspaceDir is missing
		err = service.ServiceStartup(context.Background(), application.ServiceOptions{})
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "workspaceDir")
	})
}
