package loader

import (
	"context"
	"errors"
	"path/filepath"
	"testing"

	"galaxy/client/updater"
	"galaxy/connector"
	mc "galaxy/model/client"
	"galaxy/model/report"
	"galaxy/storage"
	"galaxy/storage/fs"

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

type stubConnector struct {
	versions  []connector.VersionInfo
	versionErr error
	downloads map[string][]byte
	downloadErr error
}

func (c *stubConnector) CheckConnection() bool {
	return true
}

func (c *stubConnector) CheckVersion() ([]connector.VersionInfo, error) {
	if c.versionErr != nil {
		return nil, c.versionErr
	}
	return c.versions, nil
}

func (c *stubConnector) DownloadVersion(url string) ([]byte, error) {
	if c.downloadErr != nil {
		return nil, c.downloadErr
	}
	data, ok := c.downloads[url]
	if !ok {
		return nil, errors.New("missing download payload")
	}
	return data, nil
}

func (c *stubConnector) FetchReport(mc.GameID, uint, func(report.Report, error)) {}

type stubRunner struct {
	paths    []string
	exitCode int
	err      error
}

func (r *stubRunner) Run(_ context.Context, path string) (int, error) {
	r.paths = append(r.paths, path)
	return r.exitCode, r.err
}

func TestRunOnceFirstLaunchDownloadsAndPromotesVersion(t *testing.T) {
	t.Parallel()

	s := newTestStorage(t)
	payload := []byte("ui-binary-1.2.3")
	info := connector.VersionInfo{
		OS:       "windows",
		Arch:     "amd64",
		Kind:     connector.ArtifactKindExecutable,
		Version:  "1.2.3",
		URL:      "https://example.com/ui-1.2.3.exe",
		Checksum: connector.NewSHA256Digest(payload),
	}
	conn := &stubConnector{
		versions:  []connector.VersionInfo{info},
		downloads: map[string][]byte{info.URL: payload},
	}
	runner := &stubRunner{}
	l := &loader{
		storage:   s,
		connector: conn,
		updater:   updater.NewManager(s, conn, updater.WithPlatform("windows", "amd64")),
		runner:    runner,
	}

	err := l.runOnce(context.Background())
	require.NoError(t, err)

	state, err := s.LoadState()
	require.NoError(t, err)
	require.Equal(t, "1.2.3", state.ClientCurrentVersion)
	require.Nil(t, state.ClientNextVersion)

	expectedPath := filepath.Join(s.StorageRoot(), updater.ArtifactPath("1.2.3", "windows", "amd64", connector.ArtifactKindExecutable))
	require.Equal(t, []string{expectedPath}, runner.paths)
}

func TestRunOnceSpawnFailureClearsPendingAndKeepsCurrent(t *testing.T) {
	t.Parallel()

	s := newTestStorage(t)
	currentPath := updater.ArtifactPath("1.0.0", "windows", "amd64", connector.ArtifactKindExecutable)
	require.NoError(t, s.WriteFile(currentPath, []byte("current")))
	require.NoError(t, s.SaveState(mc.State{ClientCurrentVersion: "1.0.0"}))

	payload := []byte("ui-binary-1.1.0")
	info := connector.VersionInfo{
		OS:       "windows",
		Arch:     "amd64",
		Kind:     connector.ArtifactKindExecutable,
		Version:  "1.1.0",
		URL:      "https://example.com/ui-1.1.0.exe",
		Checksum: connector.NewSHA256Digest(payload),
	}
	conn := &stubConnector{
		versions:  []connector.VersionInfo{info},
		downloads: map[string][]byte{info.URL: payload},
	}
	manager := updater.NewManager(s, conn, updater.WithPlatform("windows", "amd64"))
	require.NoError(t, manager.CheckAndPrepareLatest())

	l := &loader{
		storage:   s,
		connector: conn,
		updater:   manager,
		runner: &stubRunner{
			err: errors.New("spawn failed"),
		},
	}

	err := l.runOnce(context.Background())
	require.Error(t, err)

	state, err := s.LoadState()
	require.NoError(t, err)
	require.Equal(t, "1.0.0", state.ClientCurrentVersion)
	require.Nil(t, state.ClientNextVersion)

	currentExists, _, err := s.FileExists(currentPath)
	require.NoError(t, err)
	require.True(t, currentExists)

	nextExists, _, err := s.FileExists(updater.ArtifactPath("1.1.0", "windows", "amd64", connector.ArtifactKindExecutable))
	require.NoError(t, err)
	require.False(t, nextExists)
}

func TestRunOnceNonZeroExitClearsPendingAndKeepsCurrent(t *testing.T) {
	t.Parallel()

	s := newTestStorage(t)
	currentPath := updater.ArtifactPath("1.0.0", "windows", "amd64", connector.ArtifactKindExecutable)
	require.NoError(t, s.WriteFile(currentPath, []byte("current")))
	require.NoError(t, s.SaveState(mc.State{ClientCurrentVersion: "1.0.0"}))

	payload := []byte("ui-binary-1.1.0")
	info := connector.VersionInfo{
		OS:       "windows",
		Arch:     "amd64",
		Kind:     connector.ArtifactKindExecutable,
		Version:  "1.1.0",
		URL:      "https://example.com/ui-1.1.0.exe",
		Checksum: connector.NewSHA256Digest(payload),
	}
	conn := &stubConnector{
		versions:  []connector.VersionInfo{info},
		downloads: map[string][]byte{info.URL: payload},
	}
	manager := updater.NewManager(s, conn, updater.WithPlatform("windows", "amd64"))
	require.NoError(t, manager.CheckAndPrepareLatest())

	l := &loader{
		storage:   s,
		connector: conn,
		updater:   manager,
		runner: &stubRunner{
			exitCode: 23,
		},
	}

	err := l.runOnce(context.Background())
	require.Error(t, err)

	state, err := s.LoadState()
	require.NoError(t, err)
	require.Equal(t, "1.0.0", state.ClientCurrentVersion)
	require.Nil(t, state.ClientNextVersion)

	nextExists, _, err := s.FileExists(updater.ArtifactPath("1.1.0", "windows", "amd64", connector.ArtifactKindExecutable))
	require.NoError(t, err)
	require.False(t, nextExists)
}

func newTestStorage(t *testing.T) *testStorage {
	t.Helper()

	root := t.TempDir()
	s, err := fs.NewFS(root)
	require.NoError(t, err)

	return &testStorage{Storage: s, root: root}
}

type testStorage struct {
	storage.Storage
	root string
}

func (s *testStorage) StorageRoot() string {
	return s.root
}
