package updates

import (
	"context"
	"encoding/json"
	"os"
	"path/filepath"
	"testing"
	"time"
)

func TestNewUpdateHistory(t *testing.T) {
	t.Run("creates directory if it doesn't exist", func(t *testing.T) {
		tmpDir := t.TempDir()
		dataDir := filepath.Join(tmpDir, "subdir", "nested")

		h, err := NewUpdateHistory(dataDir)
		if err != nil {
			t.Fatalf("NewUpdateHistory() error = %v", err)
		}
		if h == nil {
			t.Fatal("NewUpdateHistory() returned nil")
		}

		// Directory should exist
		if _, err := os.Stat(dataDir); os.IsNotExist(err) {
			t.Error("Expected directory to be created")
		}
	})

	t.Run("uses default directory when empty", func(t *testing.T) {
		// Skip if we can't create /var/lib/pulse
		if _, err := os.Stat("/var/lib"); os.IsNotExist(err) {
			t.Skip("Skipping test that requires /var/lib")
		}

		// Just verify it doesn't panic; we can't test the actual default path
		// without potentially modifying system directories
	})

	t.Run("initializes with empty cache", func(t *testing.T) {
		tmpDir := t.TempDir()
		h, err := NewUpdateHistory(tmpDir)
		if err != nil {
			t.Fatalf("NewUpdateHistory() error = %v", err)
		}

		entries := h.ListEntries(HistoryFilter{})
		if len(entries) != 0 {
			t.Errorf("Expected empty cache, got %d entries", len(entries))
		}
	})

	t.Run("loads existing entries from file", func(t *testing.T) {
		tmpDir := t.TempDir()
		logPath := filepath.Join(tmpDir, "update-history.jsonl")

		// Write some entries
		entry := UpdateHistoryEntry{
			EventID:     "test-event-1",
			Timestamp:   time.Now(),
			Action:      ActionUpdate,
			VersionFrom: "1.0.0",
			VersionTo:   "1.1.0",
			Status:      StatusSuccess,
		}
		data, _ := json.Marshal(entry)
		if err := os.WriteFile(logPath, append(data, '\n'), 0644); err != nil {
			t.Fatalf("Failed to write test file: %v", err)
		}

		h, err := NewUpdateHistory(tmpDir)
		if err != nil {
			t.Fatalf("NewUpdateHistory() error = %v", err)
		}

		entries := h.ListEntries(HistoryFilter{})
		if len(entries) != 1 {
			t.Errorf("Expected 1 entry, got %d", len(entries))
		}
		if entries[0].EventID != "test-event-1" {
			t.Errorf("Expected event ID 'test-event-1', got %q", entries[0].EventID)
		}
	})
}

func TestUpdateHistory_CreateEntry(t *testing.T) {
	ctx := context.Background()

	t.Run("creates entry with generated event ID", func(t *testing.T) {
		tmpDir := t.TempDir()
		h, _ := NewUpdateHistory(tmpDir)

		entry := UpdateHistoryEntry{
			Action:      ActionUpdate,
			VersionFrom: "1.0.0",
			VersionTo:   "1.1.0",
			Status:      StatusInProgress,
		}

		eventID, err := h.CreateEntry(ctx, entry)
		if err != nil {
			t.Fatalf("CreateEntry() error = %v", err)
		}
		if eventID == "" {
			t.Error("Expected non-empty event ID")
		}

		// Verify entry is in cache
		retrieved, err := h.GetEntry(eventID)
		if err != nil {
			t.Fatalf("GetEntry() error = %v", err)
		}
		if retrieved.VersionFrom != "1.0.0" {
			t.Errorf("VersionFrom = %q, want '1.0.0'", retrieved.VersionFrom)
		}
	})

	t.Run("uses provided event ID", func(t *testing.T) {
		tmpDir := t.TempDir()
		h, _ := NewUpdateHistory(tmpDir)

		entry := UpdateHistoryEntry{
			EventID: "my-custom-id",
			Action:  ActionUpdate,
			Status:  StatusInProgress,
		}

		eventID, err := h.CreateEntry(ctx, entry)
		if err != nil {
			t.Fatalf("CreateEntry() error = %v", err)
		}
		if eventID != "my-custom-id" {
			t.Errorf("EventID = %q, want 'my-custom-id'", eventID)
		}
	})

	t.Run("sets timestamp if not provided", func(t *testing.T) {
		tmpDir := t.TempDir()
		h, _ := NewUpdateHistory(tmpDir)

		before := time.Now()
		entry := UpdateHistoryEntry{
			Action: ActionUpdate,
			Status: StatusInProgress,
		}

		eventID, err := h.CreateEntry(ctx, entry)
		if err != nil {
			t.Fatalf("CreateEntry() error = %v", err)
		}
		after := time.Now()

		retrieved, _ := h.GetEntry(eventID)
		if retrieved.Timestamp.Before(before) || retrieved.Timestamp.After(after) {
			t.Errorf("Timestamp not set correctly: %v", retrieved.Timestamp)
		}
	})

	t.Run("preserves provided timestamp", func(t *testing.T) {
		tmpDir := t.TempDir()
		h, _ := NewUpdateHistory(tmpDir)

		customTime := time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC)
		entry := UpdateHistoryEntry{
			Timestamp: customTime,
			Action:    ActionUpdate,
			Status:    StatusInProgress,
		}

		eventID, _ := h.CreateEntry(ctx, entry)
		retrieved, _ := h.GetEntry(eventID)

		if !retrieved.Timestamp.Equal(customTime) {
			t.Errorf("Timestamp = %v, want %v", retrieved.Timestamp, customTime)
		}
	})

	t.Run("persists to file", func(t *testing.T) {
		tmpDir := t.TempDir()
		h, _ := NewUpdateHistory(tmpDir)

		entry := UpdateHistoryEntry{
			EventID: "persist-test",
			Action:  ActionUpdate,
			Status:  StatusSuccess,
		}

		_, err := h.CreateEntry(ctx, entry)
		if err != nil {
			t.Fatalf("CreateEntry() error = %v", err)
		}

		// Read file directly
		logPath := filepath.Join(tmpDir, "update-history.jsonl")
		data, err := os.ReadFile(logPath)
		if err != nil {
			t.Fatalf("Failed to read history file: %v", err)
		}

		if len(data) == 0 {
			t.Error("History file is empty")
		}

		// Parse and verify
		var parsed UpdateHistoryEntry
		if err := json.Unmarshal(data[:len(data)-1], &parsed); err != nil {
			t.Fatalf("Failed to parse persisted entry: %v", err)
		}
		if parsed.EventID != "persist-test" {
			t.Errorf("Persisted EventID = %q, want 'persist-test'", parsed.EventID)
		}
	})
}

func TestUpdateHistory_UpdateEntry(t *testing.T) {
	ctx := context.Background()

	t.Run("updates existing entry", func(t *testing.T) {
		tmpDir := t.TempDir()
		h, _ := NewUpdateHistory(tmpDir)

		// Create initial entry
		entry := UpdateHistoryEntry{
			Action: ActionUpdate,
			Status: StatusInProgress,
		}
		eventID, _ := h.CreateEntry(ctx, entry)

		// Update it
		err := h.UpdateEntry(ctx, eventID, func(e *UpdateHistoryEntry) error {
			e.Status = StatusSuccess
			e.DurationMs = 5000
			return nil
		})
		if err != nil {
			t.Fatalf("UpdateEntry() error = %v", err)
		}

		// Verify update
		retrieved, _ := h.GetEntry(eventID)
		if retrieved.Status != StatusSuccess {
			t.Errorf("Status = %v, want %v", retrieved.Status, StatusSuccess)
		}
		if retrieved.DurationMs != 5000 {
			t.Errorf("DurationMs = %d, want 5000", retrieved.DurationMs)
		}
	})

	t.Run("returns error for non-existent entry", func(t *testing.T) {
		tmpDir := t.TempDir()
		h, _ := NewUpdateHistory(tmpDir)

		err := h.UpdateEntry(ctx, "non-existent", func(e *UpdateHistoryEntry) error {
			return nil
		})
		if err == nil {
			t.Error("Expected error for non-existent entry")
		}
	})

	t.Run("persists update to file", func(t *testing.T) {
		tmpDir := t.TempDir()
		h, _ := NewUpdateHistory(tmpDir)

		// Create and update entry
		entry := UpdateHistoryEntry{
			EventID: "update-persist-test",
			Action:  ActionUpdate,
			Status:  StatusInProgress,
		}
		eventID, _ := h.CreateEntry(ctx, entry)

		h.UpdateEntry(ctx, eventID, func(e *UpdateHistoryEntry) error {
			e.Status = StatusFailed
			e.Error = &UpdateError{Message: "test error"}
			return nil
		})

		// Create new history instance to read from file
		h2, _ := NewUpdateHistory(tmpDir)
		retrieved, err := h2.GetEntry(eventID)
		if err != nil {
			t.Fatalf("Failed to get entry from new instance: %v", err)
		}
		if retrieved.Status != StatusFailed {
			t.Errorf("Persisted Status = %v, want %v", retrieved.Status, StatusFailed)
		}
		if retrieved.Error == nil || retrieved.Error.Message != "test error" {
			t.Errorf("Persisted Error not correct")
		}
	})
}

func TestUpdateHistory_GetEntry(t *testing.T) {
	ctx := context.Background()

	t.Run("returns entry by ID", func(t *testing.T) {
		tmpDir := t.TempDir()
		h, _ := NewUpdateHistory(tmpDir)

		entry := UpdateHistoryEntry{
			EventID:     "get-test",
			Action:      ActionUpdate,
			VersionFrom: "1.0.0",
			VersionTo:   "2.0.0",
			Status:      StatusSuccess,
		}
		h.CreateEntry(ctx, entry)

		retrieved, err := h.GetEntry("get-test")
		if err != nil {
			t.Fatalf("GetEntry() error = %v", err)
		}
		if retrieved.VersionFrom != "1.0.0" {
			t.Errorf("VersionFrom = %q, want '1.0.0'", retrieved.VersionFrom)
		}
		if retrieved.VersionTo != "2.0.0" {
			t.Errorf("VersionTo = %q, want '2.0.0'", retrieved.VersionTo)
		}
	})

	t.Run("returns error for non-existent entry", func(t *testing.T) {
		tmpDir := t.TempDir()
		h, _ := NewUpdateHistory(tmpDir)

		_, err := h.GetEntry("non-existent")
		if err == nil {
			t.Error("Expected error for non-existent entry")
		}
	})
}

func TestUpdateHistory_ListEntries(t *testing.T) {
	ctx := context.Background()

	t.Run("returns all entries in reverse order", func(t *testing.T) {
		tmpDir := t.TempDir()
		h, _ := NewUpdateHistory(tmpDir)

		// Create entries
		for i := 1; i <= 3; i++ {
			entry := UpdateHistoryEntry{
				EventID: "event-" + string(rune('0'+i)),
				Action:  ActionUpdate,
				Status:  StatusSuccess,
			}
			h.CreateEntry(ctx, entry)
		}

		entries := h.ListEntries(HistoryFilter{})
		if len(entries) != 3 {
			t.Fatalf("Expected 3 entries, got %d", len(entries))
		}

		// Should be in reverse order (newest first)
		if entries[0].EventID != "event-3" {
			t.Errorf("First entry should be event-3, got %s", entries[0].EventID)
		}
		if entries[2].EventID != "event-1" {
			t.Errorf("Last entry should be event-1, got %s", entries[2].EventID)
		}
	})

	t.Run("filters by status", func(t *testing.T) {
		tmpDir := t.TempDir()
		h, _ := NewUpdateHistory(tmpDir)

		// Create entries with different statuses
		h.CreateEntry(ctx, UpdateHistoryEntry{EventID: "e1", Status: StatusSuccess})
		h.CreateEntry(ctx, UpdateHistoryEntry{EventID: "e2", Status: StatusFailed})
		h.CreateEntry(ctx, UpdateHistoryEntry{EventID: "e3", Status: StatusSuccess})

		entries := h.ListEntries(HistoryFilter{Status: StatusSuccess})
		if len(entries) != 2 {
			t.Errorf("Expected 2 success entries, got %d", len(entries))
		}
		for _, e := range entries {
			if e.Status != StatusSuccess {
				t.Errorf("Entry %s has status %v, want %v", e.EventID, e.Status, StatusSuccess)
			}
		}
	})

	t.Run("filters by action", func(t *testing.T) {
		tmpDir := t.TempDir()
		h, _ := NewUpdateHistory(tmpDir)

		h.CreateEntry(ctx, UpdateHistoryEntry{EventID: "e1", Action: ActionUpdate})
		h.CreateEntry(ctx, UpdateHistoryEntry{EventID: "e2", Action: ActionRollback})
		h.CreateEntry(ctx, UpdateHistoryEntry{EventID: "e3", Action: ActionUpdate})

		entries := h.ListEntries(HistoryFilter{Action: ActionRollback})
		if len(entries) != 1 {
			t.Errorf("Expected 1 rollback entry, got %d", len(entries))
		}
		if entries[0].EventID != "e2" {
			t.Errorf("Expected e2, got %s", entries[0].EventID)
		}
	})

	t.Run("filters by deployment type", func(t *testing.T) {
		tmpDir := t.TempDir()
		h, _ := NewUpdateHistory(tmpDir)

		h.CreateEntry(ctx, UpdateHistoryEntry{EventID: "e1", DeploymentType: "docker"})
		h.CreateEntry(ctx, UpdateHistoryEntry{EventID: "e2", DeploymentType: "bare-metal"})
		h.CreateEntry(ctx, UpdateHistoryEntry{EventID: "e3", DeploymentType: "docker"})

		entries := h.ListEntries(HistoryFilter{DeploymentType: "docker"})
		if len(entries) != 2 {
			t.Errorf("Expected 2 docker entries, got %d", len(entries))
		}
	})

	t.Run("respects limit", func(t *testing.T) {
		tmpDir := t.TempDir()
		h, _ := NewUpdateHistory(tmpDir)

		for i := 0; i < 10; i++ {
			h.CreateEntry(ctx, UpdateHistoryEntry{EventID: "event"})
		}

		entries := h.ListEntries(HistoryFilter{Limit: 5})
		if len(entries) != 5 {
			t.Errorf("Expected 5 entries with limit, got %d", len(entries))
		}
	})

	t.Run("combines filters", func(t *testing.T) {
		tmpDir := t.TempDir()
		h, _ := NewUpdateHistory(tmpDir)

		h.CreateEntry(ctx, UpdateHistoryEntry{EventID: "e1", Action: ActionUpdate, Status: StatusSuccess})
		h.CreateEntry(ctx, UpdateHistoryEntry{EventID: "e2", Action: ActionUpdate, Status: StatusFailed})
		h.CreateEntry(ctx, UpdateHistoryEntry{EventID: "e3", Action: ActionRollback, Status: StatusSuccess})
		h.CreateEntry(ctx, UpdateHistoryEntry{EventID: "e4", Action: ActionUpdate, Status: StatusSuccess})

		entries := h.ListEntries(HistoryFilter{Action: ActionUpdate, Status: StatusSuccess})
		if len(entries) != 2 {
			t.Errorf("Expected 2 entries, got %d", len(entries))
		}
	})
}

func TestUpdateHistory_GetLatestSuccessful(t *testing.T) {
	ctx := context.Background()

	t.Run("returns latest successful entry", func(t *testing.T) {
		tmpDir := t.TempDir()
		h, _ := NewUpdateHistory(tmpDir)

		h.CreateEntry(ctx, UpdateHistoryEntry{EventID: "e1", Status: StatusSuccess})
		h.CreateEntry(ctx, UpdateHistoryEntry{EventID: "e2", Status: StatusFailed})
		h.CreateEntry(ctx, UpdateHistoryEntry{EventID: "e3", Status: StatusSuccess})

		entry, err := h.GetLatestSuccessful()
		if err != nil {
			t.Fatalf("GetLatestSuccessful() error = %v", err)
		}
		if entry.EventID != "e3" {
			t.Errorf("Expected e3, got %s", entry.EventID)
		}
	})

	t.Run("returns error when no successful entries", func(t *testing.T) {
		tmpDir := t.TempDir()
		h, _ := NewUpdateHistory(tmpDir)

		h.CreateEntry(ctx, UpdateHistoryEntry{EventID: "e1", Status: StatusFailed})
		h.CreateEntry(ctx, UpdateHistoryEntry{EventID: "e2", Status: StatusInProgress})

		_, err := h.GetLatestSuccessful()
		if err == nil {
			t.Error("Expected error when no successful entries")
		}
	})

	t.Run("returns error when cache is empty", func(t *testing.T) {
		tmpDir := t.TempDir()
		h, _ := NewUpdateHistory(tmpDir)

		_, err := h.GetLatestSuccessful()
		if err == nil {
			t.Error("Expected error when cache is empty")
		}
	})
}

func TestUpdateHistory_CacheManagement(t *testing.T) {
	ctx := context.Background()

	t.Run("trims cache when exceeding max size", func(t *testing.T) {
		tmpDir := t.TempDir()
		h, _ := NewUpdateHistory(tmpDir)

		// h.maxCache is 100, add more than that
		for i := 0; i < 110; i++ {
			h.CreateEntry(ctx, UpdateHistoryEntry{EventID: "event"})
		}

		entries := h.ListEntries(HistoryFilter{})
		if len(entries) > 100 {
			t.Errorf("Cache should be trimmed to 100, got %d", len(entries))
		}
	})
}

func TestUpdateHistory_LoadCache(t *testing.T) {
	t.Run("handles malformed JSON lines", func(t *testing.T) {
		tmpDir := t.TempDir()
		logPath := filepath.Join(tmpDir, "update-history.jsonl")

		// Write valid entry followed by invalid line
		validEntry := UpdateHistoryEntry{EventID: "valid", Status: StatusSuccess}
		validData, _ := json.Marshal(validEntry)
		content := string(validData) + "\n{invalid json}\n"
		os.WriteFile(logPath, []byte(content), 0644)

		// Should load without error, skipping invalid line
		h, err := NewUpdateHistory(tmpDir)
		if err != nil {
			t.Fatalf("NewUpdateHistory() error = %v", err)
		}

		entries := h.ListEntries(HistoryFilter{})
		if len(entries) != 1 {
			t.Errorf("Expected 1 valid entry, got %d", len(entries))
		}
	})

	t.Run("handles empty lines", func(t *testing.T) {
		tmpDir := t.TempDir()
		logPath := filepath.Join(tmpDir, "update-history.jsonl")

		entry := UpdateHistoryEntry{EventID: "test", Status: StatusSuccess}
		data, _ := json.Marshal(entry)
		content := "\n" + string(data) + "\n\n"
		os.WriteFile(logPath, []byte(content), 0644)

		h, _ := NewUpdateHistory(tmpDir)
		entries := h.ListEntries(HistoryFilter{})
		if len(entries) != 1 {
			t.Errorf("Expected 1 entry, got %d", len(entries))
		}
	})

	t.Run("trims entries exceeding max cache on load", func(t *testing.T) {
		tmpDir := t.TempDir()
		logPath := filepath.Join(tmpDir, "update-history.jsonl")

		// Write 150 entries (more than maxCache of 100)
		var content string
		for i := 0; i < 150; i++ {
			entry := UpdateHistoryEntry{EventID: "event", Status: StatusSuccess}
			data, _ := json.Marshal(entry)
			content += string(data) + "\n"
		}
		os.WriteFile(logPath, []byte(content), 0644)

		h, _ := NewUpdateHistory(tmpDir)
		entries := h.ListEntries(HistoryFilter{})
		if len(entries) != 100 {
			t.Errorf("Expected 100 entries after load, got %d", len(entries))
		}
	})
}

func TestUpdateHistoryEntry_Fields(t *testing.T) {
	t.Run("all fields serialize correctly", func(t *testing.T) {
		entry := UpdateHistoryEntry{
			EventID:        "test-123",
			Timestamp:      time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC),
			Action:         ActionUpdate,
			Channel:        "stable",
			VersionFrom:    "1.0.0",
			VersionTo:      "2.0.0",
			DeploymentType: "docker",
			InitiatedBy:    InitiatedByUser,
			InitiatedVia:   InitiatedViaUI,
			Status:         StatusSuccess,
			DurationMs:     5000,
			BackupPath:     "/backups/backup.tar.gz",
			LogPath:        "/logs/update.log",
			Error:          nil,
			DownloadBytes:  1048576,
			RelatedEventID: "related-456",
			Notes:          "Test update",
		}

		data, err := json.Marshal(entry)
		if err != nil {
			t.Fatalf("Marshal error: %v", err)
		}

		var parsed UpdateHistoryEntry
		if err := json.Unmarshal(data, &parsed); err != nil {
			t.Fatalf("Unmarshal error: %v", err)
		}

		if parsed.EventID != entry.EventID {
			t.Errorf("EventID mismatch")
		}
		if parsed.Action != entry.Action {
			t.Errorf("Action mismatch")
		}
		if parsed.Status != entry.Status {
			t.Errorf("Status mismatch")
		}
		if parsed.DurationMs != entry.DurationMs {
			t.Errorf("DurationMs mismatch")
		}
		if parsed.DownloadBytes != entry.DownloadBytes {
			t.Errorf("DownloadBytes mismatch")
		}
	})

	t.Run("error field serializes correctly", func(t *testing.T) {
		entry := UpdateHistoryEntry{
			EventID: "err-test",
			Status:  StatusFailed,
			Error: &UpdateError{
				Message: "download failed",
				Code:    "DOWNLOAD_ERROR",
				Details: "connection timeout",
			},
		}

		data, _ := json.Marshal(entry)
		var parsed UpdateHistoryEntry
		json.Unmarshal(data, &parsed)

		if parsed.Error == nil {
			t.Fatal("Error should not be nil")
		}
		if parsed.Error.Message != "download failed" {
			t.Errorf("Error.Message = %q, want 'download failed'", parsed.Error.Message)
		}
		if parsed.Error.Code != "DOWNLOAD_ERROR" {
			t.Errorf("Error.Code = %q, want 'DOWNLOAD_ERROR'", parsed.Error.Code)
		}
	})

	t.Run("optional fields omit when empty", func(t *testing.T) {
		entry := UpdateHistoryEntry{
			EventID: "minimal",
			Status:  StatusSuccess,
		}

		data, _ := json.Marshal(entry)
		str := string(data)

		// These optional fields should not appear
		if contains(str, "backup_path") {
			t.Error("backup_path should be omitted when empty")
		}
		if contains(str, "log_path") {
			t.Error("log_path should be omitted when empty")
		}
		if contains(str, "error") {
			t.Error("error should be omitted when nil")
		}
	})
}

func TestConstants(t *testing.T) {
	t.Run("UpdateAction constants", func(t *testing.T) {
		if ActionUpdate != "update" {
			t.Errorf("ActionUpdate = %q, want 'update'", ActionUpdate)
		}
		if ActionRollback != "rollback" {
			t.Errorf("ActionRollback = %q, want 'rollback'", ActionRollback)
		}
	})

	t.Run("UpdateStatusType constants", func(t *testing.T) {
		if StatusInProgress != "in_progress" {
			t.Errorf("StatusInProgress = %q, want 'in_progress'", StatusInProgress)
		}
		if StatusSuccess != "success" {
			t.Errorf("StatusSuccess = %q, want 'success'", StatusSuccess)
		}
		if StatusFailed != "failed" {
			t.Errorf("StatusFailed = %q, want 'failed'", StatusFailed)
		}
		if StatusRolledBack != "rolled_back" {
			t.Errorf("StatusRolledBack = %q, want 'rolled_back'", StatusRolledBack)
		}
		if StatusCancelled != "cancelled" {
			t.Errorf("StatusCancelled = %q, want 'cancelled'", StatusCancelled)
		}
	})

	t.Run("InitiatedBy constants", func(t *testing.T) {
		if InitiatedByUser != "user" {
			t.Errorf("InitiatedByUser = %q, want 'user'", InitiatedByUser)
		}
		if InitiatedByAuto != "auto" {
			t.Errorf("InitiatedByAuto = %q, want 'auto'", InitiatedByAuto)
		}
		if InitiatedByAPI != "api" {
			t.Errorf("InitiatedByAPI = %q, want 'api'", InitiatedByAPI)
		}
	})

	t.Run("InitiatedVia constants", func(t *testing.T) {
		if InitiatedViaUI != "ui" {
			t.Errorf("InitiatedViaUI = %q, want 'ui'", InitiatedViaUI)
		}
		if InitiatedViaAPI != "api" {
			t.Errorf("InitiatedViaAPI = %q, want 'api'", InitiatedViaAPI)
		}
		if InitiatedViaCLI != "cli" {
			t.Errorf("InitiatedViaCLI = %q, want 'cli'", InitiatedViaCLI)
		}
		if InitiatedViaScript != "script" {
			t.Errorf("InitiatedViaScript = %q, want 'script'", InitiatedViaScript)
		}
		if InitiatedViaWebhook != "webhook" {
			t.Errorf("InitiatedViaWebhook = %q, want 'webhook'", InitiatedViaWebhook)
		}
	})
}

func TestHistoryFilter(t *testing.T) {
	t.Run("zero value filter matches all", func(t *testing.T) {
		filter := HistoryFilter{}
		if filter.Status != "" {
			t.Error("Status should be empty by default")
		}
		if filter.Action != "" {
			t.Error("Action should be empty by default")
		}
		if filter.DeploymentType != "" {
			t.Error("DeploymentType should be empty by default")
		}
		if filter.Limit != 0 {
			t.Error("Limit should be 0 by default")
		}
	})
}

func TestUpdateError(t *testing.T) {
	t.Run("all fields serialize", func(t *testing.T) {
		err := UpdateError{
			Message: "Something went wrong",
			Code:    "ERR_001",
			Details: "Additional details",
		}

		data, _ := json.Marshal(err)
		var parsed UpdateError
		json.Unmarshal(data, &parsed)

		if parsed.Message != err.Message {
			t.Errorf("Message mismatch")
		}
		if parsed.Code != err.Code {
			t.Errorf("Code mismatch")
		}
		if parsed.Details != err.Details {
			t.Errorf("Details mismatch")
		}
	})

	t.Run("optional fields omit when empty", func(t *testing.T) {
		err := UpdateError{
			Message: "Error message",
		}

		data, _ := json.Marshal(err)
		str := string(data)

		if contains(str, "code") {
			t.Error("code should be omitted when empty")
		}
		if contains(str, "details") {
			t.Error("details should be omitted when empty")
		}
	})
}

// Helper function
func contains(s, substr string) bool {
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return true
		}
	}
	return false
}
