package loader

import (
	"fmt"
	"testing"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/container"
	fynetest "fyne.io/fyne/v2/test"
	"fyne.io/fyne/v2/theme"

	"github.com/stretchr/testify/require"
)

func TestNewLoaderConfiguresWindowGeometry(t *testing.T) {
	app := fynetest.NewApp()
	spy := &spyApp{App: app}

	l, err := NewLoader(newTestStorage(t), &stubConnector{}, spy)
	require.NoError(t, err)

	require.NotNil(t, spy.window)
	require.Same(t, spy.window, l.debugWindow)
	require.True(t, spy.window.setContentCalled)
	require.True(t, spy.window.resizeCalled)
	require.Equal(t, spy.window.content.MinSize(), spy.window.resizeSize)
	require.True(t, spy.window.fixedSizeCalled)
	require.True(t, spy.window.fixedSize)
	require.True(t, spy.window.centerOnScreenCalled)
}

func TestNewLoaderBuildsScrollableBorderLayout(t *testing.T) {
	app := fynetest.NewApp()

	l, err := NewLoader(newTestStorage(t), &stubConnector{}, app)
	require.NoError(t, err)

	content, ok := l.debugWindow.Content().(*fyne.Container)
	require.True(t, ok)
	require.Equal(t, "*layout.borderLayout", fmt.Sprintf("%T", content.Layout))
	require.Len(t, content.Objects, 2)

	logScroll, ok := content.Objects[0].(*container.Scroll)
	require.True(t, ok)
	require.Same(t, l.textGrid, logScroll.Content)
	require.Equal(t, container.ScrollBoth, logScroll.Direction)
	require.Equal(t, loaderLogViewportMinSize(app), logScroll.MinSize())
	require.Equal(t, fyne.ScrollNone, l.textGrid.Scroll)

	actionBar, ok := content.Objects[1].(*fyne.Container)
	require.True(t, ok)
	require.Len(t, actionBar.Objects, 1)

	actionRow, ok := actionBar.Objects[0].(*fyne.Container)
	require.True(t, ok)
	require.Len(t, actionRow.Objects, 1)
	require.Same(t, l.btn, actionRow.Objects[0])

	content.Resize(content.MinSize())

	require.Equal(t, fyne.NewPos(0, 0), logScroll.Position())
	require.Equal(t, content.Size().Width, logScroll.Size().Width)
	require.Equal(
		t,
		content.Size().Height-actionBar.MinSize().Height-theme.Padding(),
		logScroll.Size().Height,
	)

	require.Equal(
		t,
		fyne.NewPos(0, content.Size().Height-actionBar.MinSize().Height),
		actionBar.Position(),
	)
	require.Equal(t, content.Size().Width, actionBar.Size().Width)
	require.Equal(t, actionRow.MinSize().Width, actionRow.Size().Width)
	require.Equal(t, l.btn.MinSize().Width, l.btn.Size().Width)
	require.Equal(t, l.btn.MinSize().Height, l.btn.Size().Height)
	require.Equal(t, (content.Size().Width-actionRow.Size().Width)/2, actionRow.Position().X)
}

func TestNewLoaderInterceptsWindowCloseByHidingWindow(t *testing.T) {
	app := fynetest.NewApp()
	spy := &spyApp{App: app}

	l, err := NewLoader(newTestStorage(t), &stubConnector{}, spy)
	require.NoError(t, err)

	require.NotNil(t, spy.window)
	require.Same(t, spy.window, l.debugWindow)
	require.NotNil(t, spy.window.closeIntercept)

	spy.window.closeIntercept()

	require.Equal(t, 1, spy.window.hideCalls)
	require.Zero(t, spy.window.closeCalls)
	require.Zero(t, spy.quitCalls)
}

func TestLoaderWindowCloseQuitsApplicationAfterLaunchFailure(t *testing.T) {
	app := fynetest.NewApp()
	spy := &spyApp{App: app}

	l, err := NewLoader(newTestStorage(t), &stubConnector{}, spy)
	require.NoError(t, err)

	l.setCloseQuits(true)
	spy.window.closeIntercept()

	require.Zero(t, spy.window.hideCalls)
	require.Zero(t, spy.window.closeCalls)
	require.Equal(t, 1, spy.quitCalls)
}

type spyApp struct {
	fyne.App
	window    *spyWindow
	quitCalls int
}

func (a *spyApp) NewWindow(title string) fyne.Window {
	a.window = &spyWindow{Window: a.App.NewWindow(title)}
	return a.window
}

func (a *spyApp) Quit() {
	a.quitCalls++
	a.App.Quit()
}

type spyWindow struct {
	fyne.Window

	content              fyne.CanvasObject
	closeIntercept       func()
	resizeSize           fyne.Size
	hideCalls            int
	closeCalls           int
	setContentCalled     bool
	resizeCalled         bool
	fixedSize            bool
	fixedSizeCalled      bool
	centerOnScreenCalled bool
}

func (w *spyWindow) CenterOnScreen() {
	w.centerOnScreenCalled = true
	w.Window.CenterOnScreen()
}

func (w *spyWindow) Close() {
	w.closeCalls++
	w.Window.Close()
}

func (w *spyWindow) Hide() {
	w.hideCalls++
	w.Window.Hide()
}

func (w *spyWindow) Resize(size fyne.Size) {
	w.resizeCalled = true
	w.resizeSize = size
	w.Window.Resize(size)
}

func (w *spyWindow) SetContent(content fyne.CanvasObject) {
	w.setContentCalled = true
	w.content = content
	w.Window.SetContent(content)
}

func (w *spyWindow) SetCloseIntercept(callback func()) {
	w.closeIntercept = callback
	w.Window.SetCloseIntercept(callback)
}

func (w *spyWindow) SetFixedSize(fixed bool) {
	w.fixedSizeCalled = true
	w.fixedSize = fixed
	w.Window.SetFixedSize(fixed)
}
