package display

import (
	"testing"

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

// newTestCore creates a new core instance with essential services for testing.
func newTestCore(t *testing.T) *core.Core {
	coreInstance, err := core.New()
	require.NoError(t, err)
	return coreInstance
}

func TestNew(t *testing.T) {
	t.Run("creates service successfully", func(t *testing.T) {
		service, err := New()
		assert.NoError(t, err)
		assert.NotNil(t, service, "New() should return a non-nil service instance")
	})

	t.Run("returns independent instances", func(t *testing.T) {
		service1, err1 := New()
		service2, err2 := New()
		assert.NoError(t, err1)
		assert.NoError(t, err2)
		assert.NotSame(t, service1, service2, "New() should return different instances")
	})
}

func TestRegister(t *testing.T) {
	t.Run("registers with core successfully", func(t *testing.T) {
		coreInstance := newTestCore(t)
		service, err := Register(coreInstance)
		require.NoError(t, err)
		assert.NotNil(t, service, "Register() should return a non-nil service instance")
	})

	t.Run("returns Service type", func(t *testing.T) {
		coreInstance := newTestCore(t)
		service, err := Register(coreInstance)
		require.NoError(t, err)

		displayService, ok := service.(*Service)
		assert.True(t, ok, "Register() should return *Service type")
		assert.NotNil(t, displayService.ServiceRuntime, "ServiceRuntime should be initialized")
	})
}

func TestServiceName(t *testing.T) {
	service, err := New()
	require.NoError(t, err)

	name := service.ServiceName()
	assert.Equal(t, "forge.lthn.ai/core/gui/display", name)
}

// --- Window Option Tests ---

func TestWindowName(t *testing.T) {
	t.Run("sets window name", func(t *testing.T) {
		opt := WindowName("test-window")
		window := &Window{}

		err := opt(window)
		assert.NoError(t, err)
		assert.Equal(t, "test-window", window.Name)
	})

	t.Run("sets empty name", func(t *testing.T) {
		opt := WindowName("")
		window := &Window{}

		err := opt(window)
		assert.NoError(t, err)
		assert.Equal(t, "", window.Name)
	})
}

func TestWindowTitle(t *testing.T) {
	t.Run("sets window title", func(t *testing.T) {
		opt := WindowTitle("My Application")
		window := &Window{}

		err := opt(window)
		assert.NoError(t, err)
		assert.Equal(t, "My Application", window.Title)
	})

	t.Run("sets title with special characters", func(t *testing.T) {
		opt := WindowTitle("App - v1.0 (Beta)")
		window := &Window{}

		err := opt(window)
		assert.NoError(t, err)
		assert.Equal(t, "App - v1.0 (Beta)", window.Title)
	})
}

func TestWindowURL(t *testing.T) {
	t.Run("sets window URL", func(t *testing.T) {
		opt := WindowURL("/dashboard")
		window := &Window{}

		err := opt(window)
		assert.NoError(t, err)
		assert.Equal(t, "/dashboard", window.URL)
	})

	t.Run("sets full URL", func(t *testing.T) {
		opt := WindowURL("https://example.com/page")
		window := &Window{}

		err := opt(window)
		assert.NoError(t, err)
		assert.Equal(t, "https://example.com/page", window.URL)
	})
}

func TestWindowWidth(t *testing.T) {
	t.Run("sets window width", func(t *testing.T) {
		opt := WindowWidth(1024)
		window := &Window{}

		err := opt(window)
		assert.NoError(t, err)
		assert.Equal(t, 1024, window.Width)
	})

	t.Run("sets zero width", func(t *testing.T) {
		opt := WindowWidth(0)
		window := &Window{}

		err := opt(window)
		assert.NoError(t, err)
		assert.Equal(t, 0, window.Width)
	})

	t.Run("sets large width", func(t *testing.T) {
		opt := WindowWidth(3840)
		window := &Window{}

		err := opt(window)
		assert.NoError(t, err)
		assert.Equal(t, 3840, window.Width)
	})
}

func TestWindowHeight(t *testing.T) {
	t.Run("sets window height", func(t *testing.T) {
		opt := WindowHeight(768)
		window := &Window{}

		err := opt(window)
		assert.NoError(t, err)
		assert.Equal(t, 768, window.Height)
	})

	t.Run("sets zero height", func(t *testing.T) {
		opt := WindowHeight(0)
		window := &Window{}

		err := opt(window)
		assert.NoError(t, err)
		assert.Equal(t, 0, window.Height)
	})
}

func TestApplyOptions(t *testing.T) {
	t.Run("applies no options", func(t *testing.T) {
		window := applyOptions()
		assert.NotNil(t, window)
		assert.Equal(t, "", window.Name)
		assert.Equal(t, "", window.Title)
		assert.Equal(t, 0, window.Width)
		assert.Equal(t, 0, window.Height)
	})

	t.Run("applies single option", func(t *testing.T) {
		window := applyOptions(WindowTitle("Test"))
		assert.NotNil(t, window)
		assert.Equal(t, "Test", window.Title)
	})

	t.Run("applies multiple options", func(t *testing.T) {
		window := applyOptions(
			WindowName("main"),
			WindowTitle("My App"),
			WindowURL("/home"),
			WindowWidth(1280),
			WindowHeight(720),
		)

		assert.NotNil(t, window)
		assert.Equal(t, "main", window.Name)
		assert.Equal(t, "My App", window.Title)
		assert.Equal(t, "/home", window.URL)
		assert.Equal(t, 1280, window.Width)
		assert.Equal(t, 720, window.Height)
	})

	t.Run("handles nil options slice", func(t *testing.T) {
		window := applyOptions(nil...)
		assert.NotNil(t, window)
	})

	t.Run("applies options in order", func(t *testing.T) {
		// Later options should override earlier ones
		window := applyOptions(
			WindowTitle("First"),
			WindowTitle("Second"),
		)

		assert.NotNil(t, window)
		assert.Equal(t, "Second", window.Title)
	})
}

// --- ActionOpenWindow Tests ---

func TestActionOpenWindow(t *testing.T) {
	t.Run("creates action with options", func(t *testing.T) {
		action := ActionOpenWindow{
			WebviewWindowOptions: application.WebviewWindowOptions{
				Name:   "test",
				Title:  "Test Window",
				Width:  800,
				Height: 600,
			},
		}

		assert.Equal(t, "test", action.Name)
		assert.Equal(t, "Test Window", action.Title)
		assert.Equal(t, 800, action.Width)
		assert.Equal(t, 600, action.Height)
	})
}

// --- Tests with Mock App ---

// newServiceWithMockApp creates a Service with a mock app for testing.
func newServiceWithMockApp(t *testing.T) (*Service, *mockApp) {
	service, err := New()
	require.NoError(t, err)
	mock := newMockApp()
	service.app = mock
	return service, mock
}

func TestOpenWindow(t *testing.T) {
	t.Run("creates window with default options", func(t *testing.T) {
		service, mock := newServiceWithMockApp(t)

		err := service.OpenWindow()
		assert.NoError(t, err)

		// Verify window was created
		assert.Len(t, mock.windowManager.createdWindows, 1)
		opts := mock.windowManager.createdWindows[0]
		assert.Equal(t, "main", opts.Name)
		assert.Equal(t, "Core", opts.Title)
		assert.Equal(t, 1280, opts.Width)
		assert.Equal(t, 800, opts.Height)
		assert.Equal(t, "/", opts.URL)
	})

	t.Run("creates window with custom options", func(t *testing.T) {
		service, mock := newServiceWithMockApp(t)

		err := service.OpenWindow(
			WindowName("custom-window"),
			WindowTitle("Custom Title"),
			WindowWidth(640),
			WindowHeight(480),
			WindowURL("/custom"),
		)
		assert.NoError(t, err)

		assert.Len(t, mock.windowManager.createdWindows, 1)
		opts := mock.windowManager.createdWindows[0]
		assert.Equal(t, "custom-window", opts.Name)
		assert.Equal(t, "Custom Title", opts.Title)
		assert.Equal(t, 640, opts.Width)
		assert.Equal(t, 480, opts.Height)
		assert.Equal(t, "/custom", opts.URL)
	})
}

func TestNewWithStruct(t *testing.T) {
	t.Run("creates window from struct", func(t *testing.T) {
		service, mock := newServiceWithMockApp(t)

		opts := &Window{
			Name:   "struct-window",
			Title:  "Struct Title",
			Width:  800,
			Height: 600,
			URL:    "/struct",
		}

		_, err := service.NewWithStruct(opts)
		assert.NoError(t, err)

		assert.Len(t, mock.windowManager.createdWindows, 1)
		created := mock.windowManager.createdWindows[0]
		assert.Equal(t, "struct-window", created.Name)
		assert.Equal(t, "Struct Title", created.Title)
		assert.Equal(t, 800, created.Width)
		assert.Equal(t, 600, created.Height)
	})
}

func TestNewWithOptions(t *testing.T) {
	t.Run("creates window from options", func(t *testing.T) {
		service, mock := newServiceWithMockApp(t)

		_, err := service.NewWithOptions(
			WindowName("options-window"),
			WindowTitle("Options Title"),
		)
		assert.NoError(t, err)

		assert.Len(t, mock.windowManager.createdWindows, 1)
		opts := mock.windowManager.createdWindows[0]
		assert.Equal(t, "options-window", opts.Name)
		assert.Equal(t, "Options Title", opts.Title)
	})
}

func TestNewWithURL(t *testing.T) {
	t.Run("creates window with URL", func(t *testing.T) {
		service, mock := newServiceWithMockApp(t)

		_, err := service.NewWithURL("/dashboard")
		assert.NoError(t, err)

		assert.Len(t, mock.windowManager.createdWindows, 1)
		opts := mock.windowManager.createdWindows[0]
		assert.Equal(t, "/dashboard", opts.URL)
		assert.Equal(t, "Core", opts.Title)
		assert.Equal(t, 1280, opts.Width)
		assert.Equal(t, 900, opts.Height)
	})
}

func TestHandleOpenWindowAction(t *testing.T) {
	t.Run("creates window from message map", func(t *testing.T) {
		service, mock := newServiceWithMockApp(t)

		msg := map[string]any{
			"name": "action-window",
			"options": map[string]any{
				"Title":  "Action Title",
				"Width":  float64(1024),
				"Height": float64(768),
			},
		}

		err := service.handleOpenWindowAction(msg)
		assert.NoError(t, err)

		assert.Len(t, mock.windowManager.createdWindows, 1)
		opts := mock.windowManager.createdWindows[0]
		assert.Equal(t, "action-window", opts.Name)
		assert.Equal(t, "Action Title", opts.Title)
		assert.Equal(t, 1024, opts.Width)
		assert.Equal(t, 768, opts.Height)
	})
}

func TestMonitorScreenChanges(t *testing.T) {
	t.Run("registers theme change event", func(t *testing.T) {
		service, mock := newServiceWithMockApp(t)

		service.monitorScreenChanges()

		// Verify that an event handler was registered
		assert.Len(t, mock.eventManager.registeredEvents, 1)
	})
}

func TestSelectDirectory(t *testing.T) {
	t.Run("requires Wails runtime for file dialog", func(t *testing.T) {
		// SelectDirectory uses application.OpenFileDialog() directly
		// which requires Wails runtime. This test verifies the method exists.
		service, _ := newServiceWithMockApp(t)
		assert.NotNil(t, service.SelectDirectory)
	})
}

func TestShowEnvironmentDialog(t *testing.T) {
	t.Run("calls dialog with environment info", func(t *testing.T) {
		service, mock := newServiceWithMockApp(t)

		// This will panic because Dialog().Info() returns nil
		// We're verifying the env info is accessed, not that a dialog shows
		assert.NotPanics(t, func() {
			defer func() { recover() }() // Recover from nil dialog
			service.ShowEnvironmentDialog()
		})

		// Verify dialog was requested (even though it's nil)
		assert.Equal(t, 1, mock.dialogManager.infoDialogsCreated)
	})
}

func TestBuildMenu(t *testing.T) {
	t.Run("creates and sets menu", func(t *testing.T) {
		service, mock := newServiceWithMockApp(t)
		coreInstance := newTestCore(t)
		service.ServiceRuntime = core.NewServiceRuntime[Options](coreInstance, Options{})

		// buildMenu will panic because Menu().New() returns nil
		// We verify the menu manager was called
		assert.NotPanics(t, func() {
			defer func() { recover() }()
			service.buildMenu()
		})

		assert.Equal(t, 1, mock.menuManager.menusCreated)
	})
}

func TestSystemTray(t *testing.T) {
	t.Run("creates system tray", func(t *testing.T) {
		service, mock := newServiceWithMockApp(t)
		coreInstance := newTestCore(t)
		service.ServiceRuntime = core.NewServiceRuntime[Options](coreInstance, Options{})

		// systemTray will panic because SystemTray().New() returns nil
		// We verify the system tray manager was called
		assert.NotPanics(t, func() {
			defer func() { recover() }()
			service.systemTray()
		})

		assert.Equal(t, 1, mock.systemTrayMgr.traysCreated)
	})
}

func TestApplyOptionsWithError(t *testing.T) {
	t.Run("returns nil when option returns error", func(t *testing.T) {
		errorOption := func(o *Window) error {
			return assert.AnError
		}

		result := applyOptions(errorOption)
		assert.Nil(t, result)
	})

	t.Run("processes multiple options until error", func(t *testing.T) {
		firstOption := func(o *Window) error {
			o.Name = "first"
			return nil
		}
		errorOption := func(o *Window) error {
			return assert.AnError
		}

		result := applyOptions(firstOption, errorOption)
		assert.Nil(t, result)
		// The first option should have run before error
		// But the result is nil so we can't check
	})

	t.Run("handles empty options slice", func(t *testing.T) {
		opts := []WindowOption{}
		result := applyOptions(opts...)
		assert.NotNil(t, result)
		assert.Equal(t, "", result.Name) // Default empty values
	})
}

func TestHandleNewWorkspace(t *testing.T) {
	t.Run("opens workspace creation window", func(t *testing.T) {
		service, mock := newServiceWithMockApp(t)

		service.handleNewWorkspace()

		// Verify a window was created with correct options
		assert.Len(t, mock.windowManager.createdWindows, 1)
		opts := mock.windowManager.createdWindows[0]
		assert.Equal(t, "workspace-new", opts.Name)
		assert.Equal(t, "New Workspace", opts.Title)
		assert.Equal(t, 500, opts.Width)
		assert.Equal(t, 400, opts.Height)
		assert.Equal(t, "/workspace/new", opts.URL)
	})
}

func TestHandleListWorkspaces(t *testing.T) {
	t.Run("shows warning when workspace service not available", func(t *testing.T) {
		service, mock := newServiceWithMockApp(t)
		coreInstance := newTestCore(t)
		service.ServiceRuntime = core.NewServiceRuntime[Options](coreInstance, Options{})

		// Don't register workspace service - it won't be available
		// This will panic because Dialog().Warning() returns nil
		assert.NotPanics(t, func() {
			defer func() { recover() }()
			service.handleListWorkspaces()
		})

		assert.Equal(t, 1, mock.dialogManager.warningDialogsCreated)
	})
}

func TestParseWindowOptions(t *testing.T) {
	t.Run("parses complete options", func(t *testing.T) {
		msg := map[string]any{
			"name": "test-window",
			"options": map[string]any{
				"Title":  "Test Title",
				"Width":  float64(800),
				"Height": float64(600),
			},
		}

		opts := parseWindowOptions(msg)

		assert.Equal(t, "test-window", opts.Name)
		assert.Equal(t, "Test Title", opts.Title)
		assert.Equal(t, 800, opts.Width)
		assert.Equal(t, 600, opts.Height)
	})

	t.Run("handles missing name", func(t *testing.T) {
		msg := map[string]any{
			"options": map[string]any{
				"Title": "Test Title",
			},
		}

		opts := parseWindowOptions(msg)

		assert.Equal(t, "", opts.Name)
		assert.Equal(t, "Test Title", opts.Title)
	})

	t.Run("handles missing options", func(t *testing.T) {
		msg := map[string]any{
			"name": "test-window",
		}

		opts := parseWindowOptions(msg)

		assert.Equal(t, "test-window", opts.Name)
		assert.Equal(t, "", opts.Title)
		assert.Equal(t, 0, opts.Width)
		assert.Equal(t, 0, opts.Height)
	})

	t.Run("handles empty map", func(t *testing.T) {
		msg := map[string]any{}

		opts := parseWindowOptions(msg)

		assert.Equal(t, "", opts.Name)
		assert.Equal(t, "", opts.Title)
	})

	t.Run("handles wrong type for name", func(t *testing.T) {
		msg := map[string]any{
			"name": 123, // Wrong type - should be string
		}

		opts := parseWindowOptions(msg)

		assert.Equal(t, "", opts.Name) // Should not set name
	})

	t.Run("handles wrong type for options", func(t *testing.T) {
		msg := map[string]any{
			"name":    "test",
			"options": "not-a-map", // Wrong type
		}

		opts := parseWindowOptions(msg)

		assert.Equal(t, "test", opts.Name)
		assert.Equal(t, "", opts.Title) // Options not parsed
	})

	t.Run("handles partial width/height", func(t *testing.T) {
		msg := map[string]any{
			"options": map[string]any{
				"Width": float64(800),
				// Height missing
			},
		}

		opts := parseWindowOptions(msg)

		assert.Equal(t, 800, opts.Width)
		assert.Equal(t, 0, opts.Height)
	})
}

func TestBuildWailsWindowOptions(t *testing.T) {
	t.Run("creates default options with no args", func(t *testing.T) {
		opts := buildWailsWindowOptions()

		assert.Equal(t, "main", opts.Name)
		assert.Equal(t, "Core", opts.Title)
		assert.Equal(t, 1280, opts.Width)
		assert.Equal(t, 800, opts.Height)
		assert.Equal(t, "/", opts.URL)
	})

	t.Run("applies custom options", func(t *testing.T) {
		opts := buildWailsWindowOptions(
			WindowName("custom"),
			WindowTitle("Custom Title"),
			WindowWidth(640),
			WindowHeight(480),
			WindowURL("/custom"),
		)

		assert.Equal(t, "custom", opts.Name)
		assert.Equal(t, "Custom Title", opts.Title)
		assert.Equal(t, 640, opts.Width)
		assert.Equal(t, 480, opts.Height)
		assert.Equal(t, "/custom", opts.URL)
	})

	t.Run("skips nil options", func(t *testing.T) {
		opts := buildWailsWindowOptions(nil, WindowTitle("Test"))

		assert.Equal(t, "Test", opts.Title)
		assert.Equal(t, "main", opts.Name) // Default preserved
	})
}
