package models

import (
	"testing"
	"time"
)

func TestUpsertDockerHost(t *testing.T) {
	state := NewState()

	// Test insert new host
	host1 := DockerHost{
		ID:       "host-1",
		Hostname: "docker-host-1",
		Status:   "online",
	}
	state.UpsertDockerHost(host1)

	hosts := state.GetDockerHosts()
	if len(hosts) != 1 {
		t.Fatalf("Expected 1 host, got %d", len(hosts))
	}
	if hosts[0].ID != "host-1" {
		t.Errorf("Expected host ID 'host-1', got %q", hosts[0].ID)
	}

	// Test update existing host
	host1Updated := DockerHost{
		ID:       "host-1",
		Hostname: "docker-host-1",
		Status:   "offline",
	}
	state.UpsertDockerHost(host1Updated)

	hosts = state.GetDockerHosts()
	if len(hosts) != 1 {
		t.Fatalf("Expected 1 host after update, got %d", len(hosts))
	}
	if hosts[0].Status != "offline" {
		t.Errorf("Expected status 'offline', got %q", hosts[0].Status)
	}

	// Test CustomDisplayName preservation
	state.SetDockerHostCustomDisplayName("host-1", "My Custom Name")
	host1WithoutCustomName := DockerHost{
		ID:       "host-1",
		Hostname: "docker-host-1",
		Status:   "online",
	}
	state.UpsertDockerHost(host1WithoutCustomName)

	hosts = state.GetDockerHosts()
	if hosts[0].CustomDisplayName != "My Custom Name" {
		t.Errorf("CustomDisplayName should be preserved, got %q", hosts[0].CustomDisplayName)
	}

	// Test Hidden flag preservation
	state.SetDockerHostHidden("host-1", true)
	host1Reset := DockerHost{
		ID:       "host-1",
		Hostname: "docker-host-1",
		Status:   "online",
		Hidden:   false, // explicitly false
	}
	state.UpsertDockerHost(host1Reset)

	hosts = state.GetDockerHosts()
	if !hosts[0].Hidden {
		t.Error("Hidden flag should be preserved on upsert")
	}

	// Test PendingUninstall flag preservation
	state.SetDockerHostPendingUninstall("host-1", true)
	host1Reset2 := DockerHost{
		ID:              "host-1",
		Hostname:        "docker-host-1",
		PendingUninstall: false,
	}
	state.UpsertDockerHost(host1Reset2)

	hosts = state.GetDockerHosts()
	if !hosts[0].PendingUninstall {
		t.Error("PendingUninstall flag should be preserved on upsert")
	}

	// Test Command preservation
	cmd := &DockerHostCommandStatus{Type: "test"}
	state.SetDockerHostCommand("host-1", cmd)
	host1Reset3 := DockerHost{
		ID:       "host-1",
		Hostname: "docker-host-1",
		Command:  nil,
	}
	state.UpsertDockerHost(host1Reset3)

	hosts = state.GetDockerHosts()
	if hosts[0].Command == nil || hosts[0].Command.Type != "test" {
		t.Error("Command should be preserved on upsert")
	}
}

func TestUpsertDockerHost_Sorting(t *testing.T) {
	state := NewState()

	// Insert hosts in non-alphabetical order
	state.UpsertDockerHost(DockerHost{ID: "3", Hostname: "charlie"})
	state.UpsertDockerHost(DockerHost{ID: "1", Hostname: "alpha"})
	state.UpsertDockerHost(DockerHost{ID: "2", Hostname: "bravo"})

	hosts := state.GetDockerHosts()
	if len(hosts) != 3 {
		t.Fatalf("Expected 3 hosts, got %d", len(hosts))
	}

	// Hosts should be sorted by hostname
	if hosts[0].Hostname != "alpha" || hosts[1].Hostname != "bravo" || hosts[2].Hostname != "charlie" {
		t.Errorf("Hosts should be sorted by hostname, got: %v, %v, %v",
			hosts[0].Hostname, hosts[1].Hostname, hosts[2].Hostname)
	}
}

func TestRemoveDockerHost(t *testing.T) {
	state := NewState()

	// Insert hosts
	state.UpsertDockerHost(DockerHost{ID: "host-1", Hostname: "host1"})
	state.UpsertDockerHost(DockerHost{ID: "host-2", Hostname: "host2"})

	// Remove existing host
	removed, ok := state.RemoveDockerHost("host-1")
	if !ok {
		t.Error("Expected RemoveDockerHost to return true for existing host")
	}
	if removed.ID != "host-1" {
		t.Errorf("Expected removed host ID 'host-1', got %q", removed.ID)
	}

	hosts := state.GetDockerHosts()
	if len(hosts) != 1 {
		t.Fatalf("Expected 1 host after removal, got %d", len(hosts))
	}
	if hosts[0].ID != "host-2" {
		t.Errorf("Remaining host should be 'host-2', got %q", hosts[0].ID)
	}

	// Remove non-existing host
	removed, ok = state.RemoveDockerHost("non-existent")
	if ok {
		t.Error("Expected RemoveDockerHost to return false for non-existent host")
	}
	if removed.ID != "" {
		t.Errorf("Expected empty DockerHost for non-existent removal, got ID %q", removed.ID)
	}
}

func TestSetDockerHostStatus(t *testing.T) {
	state := NewState()

	// Test with no hosts
	changed := state.SetDockerHostStatus("host-1", "online")
	if changed {
		t.Error("SetDockerHostStatus should return false when host doesn't exist")
	}

	// Add host and set status
	state.UpsertDockerHost(DockerHost{ID: "host-1", Hostname: "host1", Status: "offline"})

	changed = state.SetDockerHostStatus("host-1", "online")
	if !changed {
		t.Error("SetDockerHostStatus should return true when host exists")
	}

	hosts := state.GetDockerHosts()
	if hosts[0].Status != "online" {
		t.Errorf("Expected status 'online', got %q", hosts[0].Status)
	}

	// Set same status (no change)
	changed = state.SetDockerHostStatus("host-1", "online")
	if !changed {
		t.Error("SetDockerHostStatus should return true even when status unchanged")
	}
}

func TestSetDockerHostHidden(t *testing.T) {
	state := NewState()

	// Test with non-existent host
	_, ok := state.SetDockerHostHidden("host-1", true)
	if ok {
		t.Error("SetDockerHostHidden should return false for non-existent host")
	}

	// Add host and set hidden
	state.UpsertDockerHost(DockerHost{ID: "host-1", Hostname: "host1", Hidden: false})

	host, ok := state.SetDockerHostHidden("host-1", true)
	if !ok {
		t.Error("SetDockerHostHidden should return true for existing host")
	}
	if !host.Hidden {
		t.Error("Expected Hidden to be true")
	}

	hosts := state.GetDockerHosts()
	if !hosts[0].Hidden {
		t.Error("Host in state should have Hidden=true")
	}

	// Set back to false
	host, ok = state.SetDockerHostHidden("host-1", false)
	if !ok || host.Hidden {
		t.Error("SetDockerHostHidden should set Hidden back to false")
	}
}

func TestSetDockerHostPendingUninstall(t *testing.T) {
	state := NewState()

	// Test with non-existent host
	_, ok := state.SetDockerHostPendingUninstall("host-1", true)
	if ok {
		t.Error("SetDockerHostPendingUninstall should return false for non-existent host")
	}

	// Add host and set pending uninstall
	state.UpsertDockerHost(DockerHost{ID: "host-1", Hostname: "host1"})

	host, ok := state.SetDockerHostPendingUninstall("host-1", true)
	if !ok {
		t.Error("SetDockerHostPendingUninstall should return true for existing host")
	}
	if !host.PendingUninstall {
		t.Error("Expected PendingUninstall to be true")
	}

	hosts := state.GetDockerHosts()
	if !hosts[0].PendingUninstall {
		t.Error("Host in state should have PendingUninstall=true")
	}
}

func TestSetDockerHostCommand(t *testing.T) {
	state := NewState()

	cmd := &DockerHostCommandStatus{Type: "upgrade", Status: "running"}

	// Test with non-existent host
	_, ok := state.SetDockerHostCommand("host-1", cmd)
	if ok {
		t.Error("SetDockerHostCommand should return false for non-existent host")
	}

	// Add host and set command
	state.UpsertDockerHost(DockerHost{ID: "host-1", Hostname: "host1"})

	host, ok := state.SetDockerHostCommand("host-1", cmd)
	if !ok {
		t.Error("SetDockerHostCommand should return true for existing host")
	}
	if host.Command == nil || host.Command.Type != "upgrade" {
		t.Error("Command should be set correctly")
	}

	// Clear command
	host, ok = state.SetDockerHostCommand("host-1", nil)
	if !ok || host.Command != nil {
		t.Error("SetDockerHostCommand should allow clearing command")
	}
}

func TestSetDockerHostCustomDisplayName(t *testing.T) {
	state := NewState()

	// Test with non-existent host
	_, ok := state.SetDockerHostCustomDisplayName("host-1", "Custom Name")
	if ok {
		t.Error("SetDockerHostCustomDisplayName should return false for non-existent host")
	}

	// Add host and set custom name
	state.UpsertDockerHost(DockerHost{ID: "host-1", Hostname: "host1"})

	host, ok := state.SetDockerHostCustomDisplayName("host-1", "My Docker Server")
	if !ok {
		t.Error("SetDockerHostCustomDisplayName should return true for existing host")
	}
	if host.CustomDisplayName != "My Docker Server" {
		t.Errorf("Expected CustomDisplayName 'My Docker Server', got %q", host.CustomDisplayName)
	}

	// Clear custom name
	host, ok = state.SetDockerHostCustomDisplayName("host-1", "")
	if !ok || host.CustomDisplayName != "" {
		t.Error("SetDockerHostCustomDisplayName should allow clearing name")
	}
}

func TestTouchDockerHost(t *testing.T) {
	state := NewState()

	now := time.Now()

	// Test with non-existent host
	ok := state.TouchDockerHost("host-1", now)
	if ok {
		t.Error("TouchDockerHost should return false for non-existent host")
	}

	// Add host and touch it
	state.UpsertDockerHost(DockerHost{ID: "host-1", Hostname: "host1"})

	later := now.Add(time.Hour)
	ok = state.TouchDockerHost("host-1", later)
	if !ok {
		t.Error("TouchDockerHost should return true for existing host")
	}

	hosts := state.GetDockerHosts()
	if !hosts[0].LastSeen.Equal(later) {
		t.Errorf("LastSeen should be updated to %v, got %v", later, hosts[0].LastSeen)
	}
}

func TestRemoveStaleDockerHosts(t *testing.T) {
	state := NewState()

	now := time.Now()
	old := now.Add(-2 * time.Hour)
	recent := now.Add(-30 * time.Minute)

	// Add hosts with different last seen times
	state.UpsertDockerHost(DockerHost{ID: "old-1", Hostname: "old1", LastSeen: old})
	state.UpsertDockerHost(DockerHost{ID: "old-2", Hostname: "old2", LastSeen: old})
	state.UpsertDockerHost(DockerHost{ID: "recent", Hostname: "recent", LastSeen: recent})

	// Remove hosts older than 1 hour
	cutoff := now.Add(-1 * time.Hour)
	removed := state.RemoveStaleDockerHosts(cutoff)

	if len(removed) != 2 {
		t.Fatalf("Expected 2 removed hosts, got %d", len(removed))
	}

	hosts := state.GetDockerHosts()
	if len(hosts) != 1 {
		t.Fatalf("Expected 1 remaining host, got %d", len(hosts))
	}
	if hosts[0].ID != "recent" {
		t.Errorf("Expected remaining host ID 'recent', got %q", hosts[0].ID)
	}

	// Remove with no stale hosts
	removed = state.RemoveStaleDockerHosts(cutoff)
	if len(removed) != 0 {
		t.Errorf("Expected 0 removed hosts, got %d", len(removed))
	}
}

func TestGetDockerHosts_Copy(t *testing.T) {
	state := NewState()

	state.UpsertDockerHost(DockerHost{ID: "host-1", Hostname: "host1"})

	hosts1 := state.GetDockerHosts()
	hosts2 := state.GetDockerHosts()

	// Modify one slice
	if len(hosts1) > 0 {
		hosts1[0].Hostname = "modified"
	}

	// Other slice should be unchanged
	if len(hosts2) > 0 && hosts2[0].Hostname == "modified" {
		t.Error("GetDockerHosts should return a copy, not the same slice")
	}

	// Original state should be unchanged
	hosts3 := state.GetDockerHosts()
	if hosts3[0].Hostname == "modified" {
		t.Error("State should be unchanged by modifications to returned slice")
	}
}

func TestAddRemovedDockerHost(t *testing.T) {
	state := NewState()

	now := time.Now()
	earlier := now.Add(-1 * time.Hour)

	// Add removed host
	entry1 := RemovedDockerHost{
		ID:        "host-1",
		Hostname:  "host1",
		RemovedAt: earlier,
	}
	state.AddRemovedDockerHost(entry1)

	removed := state.GetRemovedDockerHosts()
	if len(removed) != 1 {
		t.Fatalf("Expected 1 removed host, got %d", len(removed))
	}
	if removed[0].ID != "host-1" {
		t.Errorf("Expected ID 'host-1', got %q", removed[0].ID)
	}

	// Add another removed host (should be sorted by RemovedAt desc)
	entry2 := RemovedDockerHost{
		ID:        "host-2",
		Hostname:  "host2",
		RemovedAt: now,
	}
	state.AddRemovedDockerHost(entry2)

	removed = state.GetRemovedDockerHosts()
	if len(removed) != 2 {
		t.Fatalf("Expected 2 removed hosts, got %d", len(removed))
	}
	// Most recent should be first
	if removed[0].ID != "host-2" {
		t.Errorf("Most recently removed host should be first, got %q", removed[0].ID)
	}

	// Update existing entry (replace)
	entry1Updated := RemovedDockerHost{
		ID:        "host-1",
		Hostname:  "host1-updated",
		RemovedAt: now.Add(time.Minute), // even more recent
	}
	state.AddRemovedDockerHost(entry1Updated)

	removed = state.GetRemovedDockerHosts()
	if len(removed) != 2 {
		t.Fatalf("Expected 2 removed hosts after update, got %d", len(removed))
	}
	// host-1 should now be first (most recent)
	if removed[0].ID != "host-1" {
		t.Errorf("Updated host should be first, got %q", removed[0].ID)
	}
	if removed[0].Hostname != "host1-updated" {
		t.Errorf("Expected updated hostname, got %q", removed[0].Hostname)
	}
}

func TestRemoveRemovedDockerHost(t *testing.T) {
	state := NewState()

	now := time.Now()

	// Add some removed hosts
	state.AddRemovedDockerHost(RemovedDockerHost{ID: "host-1", RemovedAt: now})
	state.AddRemovedDockerHost(RemovedDockerHost{ID: "host-2", RemovedAt: now})

	// Remove one
	state.RemoveRemovedDockerHost("host-1")

	removed := state.GetRemovedDockerHosts()
	if len(removed) != 1 {
		t.Fatalf("Expected 1 removed host after deletion, got %d", len(removed))
	}
	if removed[0].ID != "host-2" {
		t.Errorf("Expected remaining host ID 'host-2', got %q", removed[0].ID)
	}

	// Remove non-existent (should not panic or error)
	state.RemoveRemovedDockerHost("non-existent")
	removed = state.GetRemovedDockerHosts()
	if len(removed) != 1 {
		t.Errorf("Removing non-existent should not change count, got %d", len(removed))
	}
}

func TestGetRemovedDockerHosts_Copy(t *testing.T) {
	state := NewState()

	state.AddRemovedDockerHost(RemovedDockerHost{ID: "host-1", RemovedAt: time.Now()})

	hosts1 := state.GetRemovedDockerHosts()
	hosts2 := state.GetRemovedDockerHosts()

	// Modify one slice
	if len(hosts1) > 0 {
		hosts1[0].Hostname = "modified"
	}

	// Other slice should be unchanged
	if len(hosts2) > 0 && hosts2[0].Hostname == "modified" {
		t.Error("GetRemovedDockerHosts should return a copy, not the same slice")
	}
}
