package installer

import (
	"os"
	"strings"
	"testing"

	"gitgud.io/mike/mpv-manager/pkg/constants"
	"github.com/stretchr/testify/assert"
)

func containsAppIDInCommands(commands [][]string, appID string) bool {
	for _, cmd := range commands {
		for _, arg := range cmd {
			if arg == appID {
				return true
			}
		}
	}
	return false
}

func TestNewPackageManager(t *testing.T) {
	tests := []struct {
		name       string
		family     string
		wantCmd    string
		wantFamily string
	}{
		{
			name:       "Debian family",
			family:     constants.PMFamilyDebian,
			wantCmd:    constants.CommandApt,
			wantFamily: constants.PMFamilyDebian,
		},
		{
			name:       "RHEL family",
			family:     constants.PMFamilyRHEL,
			wantCmd:    constants.CommandDnf,
			wantFamily: constants.PMFamilyRHEL,
		},
		{
			name:       "Arch family",
			family:     constants.PMFamilyArch,
			wantCmd:    constants.CommandPacman,
			wantFamily: constants.PMFamilyArch,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := NewPackageManager(tt.family)

			assert.Equal(t, tt.wantCmd, got.cmd)
			assert.Equal(t, tt.wantFamily, got.family)
		})
	}
}

func TestBuildInstallCommand(t *testing.T) {
	tests := []struct {
		name          string
		family        string
		packageName   string
		withPrivilege bool
		containsCmd   string
		containsSudo  bool
	}{
		{
			name:          "Debian install with privilege",
			family:        constants.PMFamilyDebian,
			packageName:   "mpv",
			withPrivilege: true,
			containsCmd:   "apt install -y",
			containsSudo:  true,
		},
		{
			name:          "Debian install without privilege",
			family:        constants.PMFamilyDebian,
			packageName:   "mpv",
			withPrivilege: false,
			containsCmd:   "apt install -y",
			containsSudo:  false,
		},
		{
			name:          "RHEL install with privilege",
			family:        constants.PMFamilyRHEL,
			packageName:   "mpv",
			withPrivilege: true,
			containsCmd:   "dnf install -y",
			containsSudo:  true,
		},
		{
			name:          "RHEL install without privilege",
			family:        constants.PMFamilyRHEL,
			packageName:   "mpv",
			withPrivilege: false,
			containsCmd:   "dnf install -y",
			containsSudo:  false,
		},
		{
			name:          "Arch install with privilege",
			family:        constants.PMFamilyArch,
			packageName:   "mpv",
			withPrivilege: true,
			containsCmd:   "pacman -S --noconfirm",
			containsSudo:  true,
		},
		{
			name:          "Arch install without privilege",
			family:        constants.PMFamilyArch,
			packageName:   "mpv",
			withPrivilege: false,
			containsCmd:   "pacman -S --noconfirm",
			containsSudo:  false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			pm := NewPackageManager(tt.family)
			got := pm.BuildInstallCommand(tt.packageName, tt.withPrivilege)

			assert.NotNil(t, got)

			// Check if expected command parts are in result
			result := strings.Join(got, " ")
			assert.Contains(t, result, tt.containsCmd)
			assert.Contains(t, result, tt.packageName)
			if tt.withPrivilege && os.Geteuid() != 0 {
				assert.Contains(t, result, "sudo")
			}
		})
	}
}

func TestBuildUpdateCommand(t *testing.T) {
	tests := []struct {
		name          string
		family        string
		withPrivilege bool
		containsCmd   string
		containsSudo  bool
	}{
		{
			name:          "Debian update with privilege",
			family:        constants.PMFamilyDebian,
			withPrivilege: true,
			containsCmd:   "apt update",
			containsSudo:  true,
		},
		{
			name:          "Debian update without privilege",
			family:        constants.PMFamilyDebian,
			withPrivilege: false,
			containsCmd:   "apt update",
			containsSudo:  false,
		},
		{
			name:          "RHEL update with privilege",
			family:        constants.PMFamilyRHEL,
			withPrivilege: true,
			containsCmd:   "dnf makecache",
			containsSudo:  true,
		},
		{
			name:          "RHEL update without privilege",
			family:        constants.PMFamilyRHEL,
			withPrivilege: false,
			containsCmd:   "dnf makecache",
			containsSudo:  false,
		},
		{
			name:          "RHEL update without privilege",
			family:        constants.PMFamilyRHEL,
			withPrivilege: false,
			containsCmd:   "dnf makecache",
			containsSudo:  false,
		},
		{
			name:          "Arch update with privilege",
			family:        constants.PMFamilyArch,
			withPrivilege: true,
			containsCmd:   "pacman -Sy",
			containsSudo:  true,
		},
		{
			name:          "Arch update without privilege",
			family:        constants.PMFamilyArch,
			withPrivilege: false,
			containsCmd:   "pacman -Sy",
			containsSudo:  false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			pm := NewPackageManager(tt.family)
			got := pm.BuildUpdateCommand(tt.withPrivilege)

			assert.NotNil(t, got)

			result := strings.Join(got, " ")
			assert.Contains(t, result, tt.containsCmd)
			if tt.withPrivilege && os.Geteuid() != 0 {
				assert.Contains(t, result, "sudo")
			}
		})
	}
}

func TestBuildUpgradeCommand(t *testing.T) {
	tests := []struct {
		name          string
		family        string
		packageName   string
		withPrivilege bool
		containsCmd   string
		containsSudo  bool
	}{
		{
			name:          "Debian upgrade with privilege and package",
			family:        constants.PMFamilyDebian,
			packageName:   "mpv",
			withPrivilege: true,
			containsCmd:   "apt upgrade -y",
			containsSudo:  true,
		},
		{
			name:          "Debian upgrade without privilege and package",
			family:        constants.PMFamilyDebian,
			packageName:   "mpv",
			withPrivilege: false,
			containsCmd:   "apt upgrade -y",
			containsSudo:  false,
		},
		{
			name:          "Debian upgrade without package",
			family:        constants.PMFamilyDebian,
			packageName:   "",
			withPrivilege: true,
			containsCmd:   "apt upgrade -y",
			containsSudo:  true,
		},
		{
			name:          "Arch upgrade with privilege",
			family:        constants.PMFamilyArch,
			packageName:   "mpv",
			withPrivilege: true,
			containsCmd:   "pacman -Sy --noconfirm",
			containsSudo:  true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			pm := NewPackageManager(tt.family)
			got := pm.BuildUpgradeCommand(tt.packageName, tt.withPrivilege)

			assert.NotNil(t, got)
			result := strings.Join(got, " ")
			assert.Contains(t, result, tt.containsCmd)
			if tt.packageName != "" {
				assert.Contains(t, result, tt.packageName)
			}
			if tt.withPrivilege && os.Geteuid() != 0 {
				assert.Contains(t, result, "sudo")
			}
		})
	}
}

func TestBuildRemoveCommand(t *testing.T) {
	tests := []struct {
		name          string
		family        string
		packageName   string
		withPrivilege bool
		containsCmd   string
		containsSudo  bool
	}{
		{
			name:          "Debian remove with privilege",
			family:        constants.PMFamilyDebian,
			packageName:   "mpv",
			withPrivilege: true,
			containsCmd:   "apt remove -y",
			containsSudo:  true,
		},
		{
			name:          "Debian remove without privilege",
			family:        constants.PMFamilyDebian,
			packageName:   "mpv",
			withPrivilege: false,
			containsCmd:   "apt remove -y",
			containsSudo:  false,
		},
		{
			name:          "RHEL remove with privilege",
			family:        constants.PMFamilyRHEL,
			packageName:   "mpv",
			withPrivilege: true,
			containsCmd:   "dnf remove -y",
			containsSudo:  true,
		},
		{
			name:          "Arch remove with privilege",
			family:        constants.PMFamilyArch,
			packageName:   "mpv",
			withPrivilege: true,
			containsCmd:   "pacman -Rn",
			containsSudo:  true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			pm := NewPackageManager(tt.family)
			got := pm.BuildRemoveCommand(tt.packageName, tt.withPrivilege)

			assert.NotNil(t, got)
			result := strings.Join(got, " ")
			assert.Contains(t, result, tt.containsCmd)
			assert.Contains(t, result, tt.packageName)
			if tt.withPrivilege && os.Geteuid() != 0 {
				assert.Contains(t, result, "sudo")
			}
		})
	}
}

func TestGetCommandName(t *testing.T) {
	tests := []struct {
		name    string
		family  string
		wantCmd string
	}{
		{
			name:    "Debian family command name",
			family:  constants.PMFamilyDebian,
			wantCmd: constants.CommandApt,
		},
		{
			name:    "RHEL family command name",
			family:  constants.PMFamilyRHEL,
			wantCmd: constants.CommandDnf,
		},
		{
			name:    "Arch family command name",
			family:  constants.PMFamilyArch,
			wantCmd: constants.CommandPacman,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			pm := NewPackageManager(tt.family)
			got := pm.GetCommandName()

			assert.Equal(t, tt.wantCmd, got)
		})
	}
}

func TestGetFamily(t *testing.T) {
	tests := []struct {
		name       string
		family     string
		wantFamily string
	}{
		{
			name:       "Debian family getter",
			family:     constants.PMFamilyDebian,
			wantFamily: constants.PMFamilyDebian,
		},
		{
			name:       "RHEL family getter",
			family:     constants.PMFamilyRHEL,
			wantFamily: constants.PMFamilyRHEL,
		},
		{
			name:       "Arch family getter",
			family:     constants.PMFamilyArch,
			wantFamily: constants.PMFamilyArch,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			pm := NewPackageManager(tt.family)
			got := pm.GetFamily()

			assert.Equal(t, tt.wantFamily, got)
		})
	}
}

func TestNewFlatpakManager(t *testing.T) {
	t.Run("Create Flatpak manager", func(t *testing.T) {
		fm := NewFlatpakManager()
		assert.NotNil(t, fm)
	})
}

func TestFlatpakBuildInstallCommands(t *testing.T) {
	tests := []struct {
		name            string
		appID           string
		enableFlathub   bool
		expectedCount   int
		containsFlathub bool
		containsInstall bool
	}{
		{
			name:            "Install with Flathub enabled",
			appID:           "io.mpv.Mpv",
			enableFlathub:   true,
			expectedCount:   2,
			containsFlathub: true,
			containsInstall: true,
		},
		{
			name:            "Install without Flathub",
			appID:           "io.mpv.Mpv",
			enableFlathub:   false,
			expectedCount:   1,
			containsFlathub: false,
			containsInstall: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			fm := NewFlatpakManager()
			got := fm.BuildInstallCommands(tt.appID, tt.enableFlathub)

			assert.Equal(t, tt.expectedCount, len(got))
			assert.True(t, containsAppIDInCommands(got, tt.appID))
			assert.Equal(t, tt.containsFlathub, containsCommandArray(got, "flatpak", "remote-add", "--if-not-exists", "flathub"))
			assert.Equal(t, tt.containsInstall, containsCommandArray(got, "flatpak", "install"))
		})
	}
}

func TestFlatpakBuildUpdateCommand(t *testing.T) {
	tests := []struct {
		name     string
		appID    string
		contains bool
	}{
		{
			name:     "Update with app ID",
			appID:    "io.mpv.Mpv",
			contains: true,
		},
		{
			name:     "Update without app ID",
			appID:    "",
			contains: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			fm := NewFlatpakManager()
			got := fm.BuildUpdateCommand(tt.appID)

			assert.NotNil(t, got)
			assert.Contains(t, got, "flatpak")
			assert.Contains(t, got, "update")
			assert.Equal(t, tt.contains, containsItem(got, tt.appID))
		})
	}
}

func TestFlatpakBuildRemoveCommand(t *testing.T) {
	t.Run("Build Flatpak remove command", func(t *testing.T) {
		fm := NewFlatpakManager()
		appID := "io.mpv.Mpv"
		got := fm.BuildRemoveCommand(appID)

		assert.NotNil(t, got)
		assert.Contains(t, got, "flatpak")
		assert.Contains(t, got, "uninstall")
		assert.Contains(t, got, appID)
	})
}

func TestFlatpakBuildOverrideCommand(t *testing.T) {
	t.Run("Build Flatpak override command", func(t *testing.T) {
		fm := NewFlatpakManager()
		override := "--filesystem=xdg-config/mpv:ro"
		appID := "io.mpv.Mpv"
		got := fm.BuildOverrideCommand(override, appID)

		assert.NotNil(t, got)
		assert.Contains(t, got, "flatpak")
		assert.Contains(t, got, "override")
		assert.Contains(t, got, override)
		assert.Contains(t, got, appID)
	})
}

// Helper functions for tests
func containsItem(cmds []string, item string) bool {
	for _, cmd := range cmds {
		if cmd == item {
			return true
		}
	}
	return false
}

func containsCommandArray(cmdSets [][]string, parts ...string) bool {
	for _, cmds := range cmdSets {
		allMatch := true
		for _, part := range parts {
			if !containsItem(cmds, part) {
				allMatch = false
				break
			}
		}
		if allMatch {
			return true
		}
	}
	return false
}
