package resources

import (
	"testing"
	"time"
)

func TestStoreUpsertAndGet(t *testing.T) {
	store := NewStore()

	r := Resource{
		ID:           "test-1",
		Type:         ResourceTypeNode,
		Name:         "node1",
		PlatformType: PlatformProxmoxPVE,
		SourceType:   SourceAPI,
		Status:       StatusOnline,
		LastSeen:     time.Now(),
	}

	id := store.Upsert(r)
	if id != "test-1" {
		t.Errorf("Expected ID test-1, got %s", id)
	}

	retrieved, ok := store.Get("test-1")
	if !ok {
		t.Fatal("Failed to retrieve resource")
	}
	if retrieved.Name != "node1" {
		t.Errorf("Expected name node1, got %s", retrieved.Name)
	}
}

func TestStoreGetAll(t *testing.T) {
	store := NewStore()

	store.Upsert(Resource{ID: "1", Type: ResourceTypeNode, LastSeen: time.Now()})
	store.Upsert(Resource{ID: "2", Type: ResourceTypeVM, LastSeen: time.Now()})
	store.Upsert(Resource{ID: "3", Type: ResourceTypeContainer, LastSeen: time.Now()})

	all := store.GetAll()
	if len(all) != 3 {
		t.Errorf("Expected 3 resources, got %d", len(all))
	}
}

func TestStoreGetByType(t *testing.T) {
	store := NewStore()

	store.Upsert(Resource{ID: "node1", Type: ResourceTypeNode, LastSeen: time.Now()})
	store.Upsert(Resource{ID: "node2", Type: ResourceTypeNode, LastSeen: time.Now()})
	store.Upsert(Resource{ID: "vm1", Type: ResourceTypeVM, LastSeen: time.Now()})

	nodes := store.GetByType(ResourceTypeNode)
	if len(nodes) != 2 {
		t.Errorf("Expected 2 nodes, got %d", len(nodes))
	}

	vms := store.GetByType(ResourceTypeVM)
	if len(vms) != 1 {
		t.Errorf("Expected 1 VM, got %d", len(vms))
	}
}

func TestStoreGetByPlatform(t *testing.T) {
	store := NewStore()

	store.Upsert(Resource{ID: "1", PlatformType: PlatformProxmoxPVE, LastSeen: time.Now()})
	store.Upsert(Resource{ID: "2", PlatformType: PlatformProxmoxPVE, LastSeen: time.Now()})
	store.Upsert(Resource{ID: "3", PlatformType: PlatformDocker, LastSeen: time.Now()})

	pve := store.GetByPlatform(PlatformProxmoxPVE)
	if len(pve) != 2 {
		t.Errorf("Expected 2 PVE resources, got %d", len(pve))
	}

	docker := store.GetByPlatform(PlatformDocker)
	if len(docker) != 1 {
		t.Errorf("Expected 1 Docker resource, got %d", len(docker))
	}
}

func TestStoreGetInfrastructureAndWorkloads(t *testing.T) {
	store := NewStore()

	store.Upsert(Resource{ID: "node1", Type: ResourceTypeNode, LastSeen: time.Now()})
	store.Upsert(Resource{ID: "host1", Type: ResourceTypeHost, LastSeen: time.Now()})
	store.Upsert(Resource{ID: "vm1", Type: ResourceTypeVM, LastSeen: time.Now()})
	store.Upsert(Resource{ID: "ct1", Type: ResourceTypeContainer, LastSeen: time.Now()})
	store.Upsert(Resource{ID: "dc1", Type: ResourceTypeDockerContainer, LastSeen: time.Now()})

	infra := store.GetInfrastructure()
	if len(infra) != 2 {
		t.Errorf("Expected 2 infrastructure resources, got %d", len(infra))
	}

	workloads := store.GetWorkloads()
	if len(workloads) != 3 {
		t.Errorf("Expected 3 workload resources, got %d", len(workloads))
	}
}

func TestStoreGetChildren(t *testing.T) {
	store := NewStore()

	store.Upsert(Resource{ID: "node1", Type: ResourceTypeNode, LastSeen: time.Now()})
	store.Upsert(Resource{ID: "vm1", Type: ResourceTypeVM, ParentID: "node1", LastSeen: time.Now()})
	store.Upsert(Resource{ID: "vm2", Type: ResourceTypeVM, ParentID: "node1", LastSeen: time.Now()})
	store.Upsert(Resource{ID: "vm3", Type: ResourceTypeVM, ParentID: "node2", LastSeen: time.Now()})

	children := store.GetChildren("node1")
	if len(children) != 2 {
		t.Errorf("Expected 2 children of node1, got %d", len(children))
	}
}

func TestStoreRemove(t *testing.T) {
	store := NewStore()

	store.Upsert(Resource{ID: "test-1", LastSeen: time.Now()})
	store.Upsert(Resource{ID: "test-2", LastSeen: time.Now()})

	if len(store.GetAll()) != 2 {
		t.Fatal("Expected 2 resources before remove")
	}

	store.Remove("test-1")

	if len(store.GetAll()) != 1 {
		t.Error("Expected 1 resource after remove")
	}

	_, ok := store.Get("test-1")
	if ok {
		t.Error("Removed resource should not be retrievable")
	}
}

func TestDeduplicationByHostname(t *testing.T) {
	store := NewStore()

	now := time.Now()

	// Add a Proxmox node (API source)
	nodeResource := Resource{
		ID:           "pve1/node/server1",
		Type:         ResourceTypeNode,
		Name:         "server1",
		PlatformType: PlatformProxmoxPVE,
		SourceType:   SourceAPI,
		Status:       StatusOnline,
		CPU:          &MetricValue{Current: 50.0},
		LastSeen:     now,
		Identity: &ResourceIdentity{
			Hostname: "server1",
		},
	}
	store.Upsert(nodeResource)

	// Add a host agent for the same server (agent source)
	// Different types should coexist, not merge
	hostResource := Resource{
		ID:           "host-agent/server1",
		Type:         ResourceTypeHost,
		Name:         "server1",
		PlatformType: PlatformHostAgent,
		SourceType:   SourceAgent,
		Status:       StatusOnline,
		CPU:          &MetricValue{Current: 55.0},
		LastSeen:     now,
		Identity: &ResourceIdentity{
			Hostname: "server1",
		},
	}
	store.Upsert(hostResource)

	// Different types should coexist (node + host = 2 resources)
	all := store.GetAll()
	if len(all) != 2 {
		t.Errorf("Expected 2 resources (node + host coexist), got %d", len(all))
	}

	// Both should be retrievable
	_, ok := store.Get("pve1/node/server1")
	if !ok {
		t.Error("Node resource should be retrievable")
	}
	_, ok = store.Get("host-agent/server1")
	if !ok {
		t.Error("Host resource should be retrievable")
	}

	// Now test same-type deduplication: add another node with same hostname
	nodeResource2 := Resource{
		ID:           "pve2/node/server1",
		Type:         ResourceTypeNode,
		Name:         "server1",
		PlatformType: PlatformProxmoxPVE,
		SourceType:   SourceAPI,
		Status:       StatusOnline,
		CPU:          &MetricValue{Current: 60.0},
		LastSeen:     now.Add(time.Second), // Newer
		Identity: &ResourceIdentity{
			Hostname: "server1",
		},
	}
	store.Upsert(nodeResource2)

	// Should still have 2 (newer node replaces old node, host remains)
	all = store.GetAll()
	if len(all) != 2 {
		t.Errorf("Expected 2 resources after same-type dedup, got %d", len(all))
	}

	// The newer node should have replaced the old one
	r, ok := store.Get("pve2/node/server1")
	if !ok {
		t.Error("Newer node should be present")
	}
	if r.CPU.Current != 60.0 {
		t.Errorf("Expected CPU 60.0 from newer node, got %f", r.CPU.Current)
	}
}

func TestDeduplicationByMachineID(t *testing.T) {
	store := NewStore()

	now := time.Now()
	machineID := "abc-123-def-456"

	// Add a Docker host
	dockerHost := Resource{
		ID:           "docker-host-1",
		Type:         ResourceTypeDockerHost,
		Name:         "server-different-name",
		PlatformType: PlatformDocker,
		SourceType:   SourceAgent,
		Status:       StatusOnline,
		LastSeen:     now,
		Identity: &ResourceIdentity{
			Hostname:  "server-different-name",
			MachineID: machineID,
		},
	}
	store.Upsert(dockerHost)

	// Add a host agent with the same machine ID but different type
	// Different types should coexist
	hostAgent := Resource{
		ID:           "host-agent-1",
		Type:         ResourceTypeHost,
		Name:         "server-production",
		PlatformType: PlatformHostAgent,
		SourceType:   SourceAgent,
		Status:       StatusOnline,
		LastSeen:     now.Add(time.Second),
		Identity: &ResourceIdentity{
			Hostname:  "server-production",
			MachineID: machineID,
		},
	}
	store.Upsert(hostAgent)

	// Different types should coexist (docker-host + host = 2 resources)
	all := store.GetAll()
	if len(all) != 2 {
		t.Errorf("Expected 2 resources (different types coexist with same machineID), got %d", len(all))
	}

	// Now test same-type deduplication: add another host with same machine ID
	hostAgent2 := Resource{
		ID:           "host-agent-2",
		Type:         ResourceTypeHost, // Same type as first host
		Name:         "server-newer",
		PlatformType: PlatformHostAgent,
		SourceType:   SourceAgent,
		Status:       StatusOnline,
		LastSeen:     now.Add(2 * time.Second), // Newer
		Identity: &ResourceIdentity{
			Hostname:  "server-newer",
			MachineID: machineID,
		},
	}
	store.Upsert(hostAgent2)

	// Should still have 2 (newer host replaces old host, docker-host remains)
	all = store.GetAll()
	if len(all) != 2 {
		t.Errorf("Expected 2 resources after same-type dedup, got %d", len(all))
	}
}

func TestDeduplicationByIP(t *testing.T) {
	store := NewStore()

	now := time.Now()
	sharedIP := "192.168.1.100"

	// Add a Proxmox node
	node := Resource{
		ID:           "node-1",
		Type:         ResourceTypeNode,
		Name:         "pve-node",
		PlatformType: PlatformProxmoxPVE,
		SourceType:   SourceAPI,
		Status:       StatusOnline,
		LastSeen:     now,
		Identity: &ResourceIdentity{
			Hostname: "pve-node",
			IPs:      []string{sharedIP},
		},
	}
	store.Upsert(node)

	// Add a host agent with the same IP but different type
	// Different types should coexist
	host := Resource{
		ID:           "host-1",
		Type:         ResourceTypeHost,
		Name:         "different-hostname",
		PlatformType: PlatformHostAgent,
		SourceType:   SourceAgent,
		Status:       StatusOnline,
		LastSeen:     now,
		Identity: &ResourceIdentity{
			Hostname: "different-hostname",
			IPs:      []string{sharedIP},
		},
	}
	store.Upsert(host)

	// Different types should coexist (node + host = 2 resources)
	all := store.GetAll()
	if len(all) != 2 {
		t.Errorf("Expected 2 resources (different types coexist), got %d", len(all))
	}

	// Both should be retrievable
	_, ok := store.Get("node-1")
	if !ok {
		t.Error("Node should be retrievable")
	}
	_, ok = store.Get("host-1")
	if !ok {
		t.Error("Host should be retrievable")
	}
}

func TestNoDeduplicationForWorkloads(t *testing.T) {
	store := NewStore()

	now := time.Now()

	// VMs with the same hostname should NOT be deduplicated
	// (they're workloads, not infrastructure)
	vm1 := Resource{
		ID:           "pve1/vm/100",
		Type:         ResourceTypeVM,
		Name:         "webserver",
		PlatformType: PlatformProxmoxPVE,
		SourceType:   SourceAPI,
		LastSeen:     now,
		Identity: &ResourceIdentity{
			Hostname: "webserver", // Same hostname
		},
	}
	store.Upsert(vm1)

	vm2 := Resource{
		ID:           "pve2/vm/100",
		Type:         ResourceTypeVM,
		Name:         "webserver",
		PlatformType: PlatformProxmoxPVE,
		SourceType:   SourceAPI,
		LastSeen:     now,
		Identity: &ResourceIdentity{
			Hostname: "webserver", // Same hostname
		},
	}
	store.Upsert(vm2)

	// Both VMs should exist (workloads are not deduplicated)
	all := store.GetAll()
	if len(all) != 2 {
		t.Errorf("Expected 2 VMs (no dedup for workloads), got %d", len(all))
	}
}

func TestNoDeduplicationForLocalhost(t *testing.T) {
	store := NewStore()

	now := time.Now()

	host1 := Resource{
		ID:           "host-1",
		Type:         ResourceTypeHost,
		Name:         "server1",
		PlatformType: PlatformHostAgent,
		SourceType:   SourceAgent,
		LastSeen:     now,
		Identity: &ResourceIdentity{
			Hostname: "server1",
			IPs:      []string{"127.0.0.1", "192.168.1.1"},
		},
	}
	store.Upsert(host1)

	host2 := Resource{
		ID:           "host-2",
		Type:         ResourceTypeHost,
		Name:         "server2",
		PlatformType: PlatformHostAgent,
		SourceType:   SourceAgent,
		LastSeen:     now,
		Identity: &ResourceIdentity{
			Hostname: "server2",
			IPs:      []string{"127.0.0.1", "192.168.1.2"}, // Both have localhost
		},
	}
	store.Upsert(host2)

	// Both should exist (127.0.0.1 shouldn't trigger dedup)
	all := store.GetAll()
	if len(all) != 2 {
		t.Errorf("Expected 2 hosts (localhost shouldn't dedup), got %d", len(all))
	}
}

func TestStoreStats(t *testing.T) {
	store := NewStore()

	store.Upsert(Resource{ID: "1", Type: ResourceTypeNode, PlatformType: PlatformProxmoxPVE, LastSeen: time.Now()})
	store.Upsert(Resource{ID: "2", Type: ResourceTypeNode, PlatformType: PlatformProxmoxPVE, LastSeen: time.Now()})
	store.Upsert(Resource{ID: "3", Type: ResourceTypeVM, PlatformType: PlatformProxmoxPVE, LastSeen: time.Now()})
	store.Upsert(Resource{ID: "4", Type: ResourceTypeDockerHost, PlatformType: PlatformDocker, LastSeen: time.Now()})

	stats := store.GetStats()

	if stats.TotalResources != 4 {
		t.Errorf("Expected 4 total resources, got %d", stats.TotalResources)
	}
	if stats.ByType[ResourceTypeNode] != 2 {
		t.Errorf("Expected 2 nodes, got %d", stats.ByType[ResourceTypeNode])
	}
	if stats.ByPlatform[PlatformProxmoxPVE] != 3 {
		t.Errorf("Expected 3 PVE resources, got %d", stats.ByPlatform[PlatformProxmoxPVE])
	}
}

func TestStoreQuery(t *testing.T) {
	store := NewStore()

	store.Upsert(Resource{
		ID:           "1",
		Type:         ResourceTypeNode,
		PlatformType: PlatformProxmoxPVE,
		Status:       StatusOnline,
		LastSeen:     time.Now(),
	})
	store.Upsert(Resource{
		ID:           "2",
		Type:         ResourceTypeVM,
		PlatformType: PlatformProxmoxPVE,
		Status:       StatusRunning,
		ParentID:     "1",
		LastSeen:     time.Now(),
	})
	store.Upsert(Resource{
		ID:           "3",
		Type:         ResourceTypeVM,
		PlatformType: PlatformProxmoxPVE,
		Status:       StatusStopped,
		ParentID:     "1",
		LastSeen:     time.Now(),
	})
	store.Upsert(Resource{
		ID:           "4",
		Type:         ResourceTypeDockerContainer,
		PlatformType: PlatformDocker,
		Status:       StatusRunning,
		LastSeen:     time.Now(),
	})

	// Query by type
	vms := store.Query().OfType(ResourceTypeVM).Execute()
	if len(vms) != 2 {
		t.Errorf("Expected 2 VMs, got %d", len(vms))
	}

	// Query by status
	running := store.Query().WithStatus(StatusRunning).Execute()
	if len(running) != 2 {
		t.Errorf("Expected 2 running resources, got %d", len(running))
	}

	// Query by platform
	pve := store.Query().FromPlatform(PlatformProxmoxPVE).Execute()
	if len(pve) != 3 {
		t.Errorf("Expected 3 PVE resources, got %d", len(pve))
	}

	// Query by parent
	node1Children := store.Query().WithParent("1").Execute()
	if len(node1Children) != 2 {
		t.Errorf("Expected 2 children of node 1, got %d", len(node1Children))
	}

	// Combined query
	runningVMs := store.Query().
		OfType(ResourceTypeVM).
		WithStatus(StatusRunning).
		Execute()
	if len(runningVMs) != 1 {
		t.Errorf("Expected 1 running VM, got %d", len(runningVMs))
	}

	// Count
	count := store.Query().OfType(ResourceTypeVM).Count()
	if count != 2 {
		t.Errorf("Expected count 2, got %d", count)
	}

	// Limit
	limited := store.Query().Limit(2).Execute()
	if len(limited) > 2 {
		t.Errorf("Expected at most 2 results, got %d", len(limited))
	}
}

func TestMarkStale(t *testing.T) {
	store := NewStore()

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

	store.Upsert(Resource{
		ID:       "old-1",
		Status:   StatusOnline,
		LastSeen: old,
	})
	store.Upsert(Resource{
		ID:       "recent-1",
		Status:   StatusOnline,
		LastSeen: recent,
	})

	stale := store.MarkStale(time.Hour)

	if len(stale) != 1 {
		t.Errorf("Expected 1 stale resource, got %d", len(stale))
	}

	r, _ := store.Get("old-1")
	if r.Status != StatusDegraded {
		t.Errorf("Expected stale resource to be degraded, got %s", r.Status)
	}

	r, _ = store.Get("recent-1")
	if r.Status != StatusOnline {
		t.Errorf("Recent resource should still be online, got %s", r.Status)
	}
}

func TestPruneStale(t *testing.T) {
	store := NewStore()

	veryOld := time.Now().Add(-48 * time.Hour)
	old := time.Now().Add(-2 * time.Hour)
	recent := time.Now()

	store.Upsert(Resource{ID: "very-old", LastSeen: veryOld})
	store.Upsert(Resource{ID: "old", LastSeen: old})
	store.Upsert(Resource{ID: "recent", LastSeen: recent})

	removed := store.PruneStale(time.Hour, 24*time.Hour)

	if len(removed) != 1 {
		t.Errorf("Expected 1 removed resource, got %d", len(removed))
	}

	if len(store.GetAll()) != 2 {
		t.Errorf("Expected 2 remaining resources, got %d", len(store.GetAll()))
	}
}

func TestAPIToAgentPreference(t *testing.T) {
	store := NewStore()

	now := time.Now()

	// First, add an API-sourced host
	apiResource := Resource{
		ID:           "api-host",
		Type:         ResourceTypeHost, // Same type as agent
		Name:         "server",
		PlatformType: PlatformProxmoxPVE,
		SourceType:   SourceAPI,
		CPU:          &MetricValue{Current: 50.0},
		LastSeen:     now,
		Identity: &ResourceIdentity{
			Hostname: "server",
		},
	}
	store.Upsert(apiResource)

	// Then, add an agent resource for the same machine (same type, different source)
	agentResource := Resource{
		ID:           "agent-host",
		Type:         ResourceTypeHost, // Same type
		Name:         "server",
		PlatformType: PlatformHostAgent,
		SourceType:   SourceAgent,
		CPU:          &MetricValue{Current: 55.0},
		LastSeen:     now,
		Identity: &ResourceIdentity{
			Hostname: "server",
		},
	}
	store.Upsert(agentResource)

	// Only agent resource should exist (same type = dedup, agent preferred)
	all := store.GetAll()
	if len(all) != 1 {
		t.Fatalf("Expected 1 resource (same type dedup), got %d", len(all))
	}

	if all[0].SourceType != SourceAgent {
		t.Errorf("Expected agent source type (preferred), got %s", all[0].SourceType)
	}
}

func TestGetTopByCPU(t *testing.T) {
	store := NewStore()
	now := time.Now()

	store.Upsert(Resource{
		ID:       "vm1",
		Type:     ResourceTypeVM,
		Name:     "low-cpu-vm",
		CPU:      &MetricValue{Current: 20.0},
		LastSeen: now,
	})
	store.Upsert(Resource{
		ID:       "vm2",
		Type:     ResourceTypeVM,
		Name:     "high-cpu-vm",
		CPU:      &MetricValue{Current: 85.0},
		LastSeen: now,
	})
	store.Upsert(Resource{
		ID:       "node1",
		Type:     ResourceTypeNode,
		Name:     "busy-node",
		CPU:      &MetricValue{Current: 75.0},
		LastSeen: now,
	})

	// Get top 2 by CPU
	top := store.GetTopByCPU(2, nil)
	if len(top) != 2 {
		t.Fatalf("Expected 2 resources, got %d", len(top))
	}
	if top[0].Name != "high-cpu-vm" {
		t.Errorf("Expected high-cpu-vm first, got %s", top[0].Name)
	}
	if top[1].Name != "busy-node" {
		t.Errorf("Expected busy-node second, got %s", top[1].Name)
	}

	// Filter by type
	topVMs := store.GetTopByCPU(10, []ResourceType{ResourceTypeVM})
	if len(topVMs) != 2 {
		t.Errorf("Expected 2 VMs, got %d", len(topVMs))
	}
}

func TestGetRelated(t *testing.T) {
	store := NewStore()
	now := time.Now()

	store.Upsert(Resource{
		ID:        "node1",
		Type:      ResourceTypeNode,
		Name:      "parent-node",
		ClusterID: "cluster1",
		LastSeen:  now,
	})
	store.Upsert(Resource{
		ID:        "vm1",
		Type:      ResourceTypeVM,
		Name:      "child-vm-1",
		ParentID:  "node1",
		ClusterID: "cluster1",
		LastSeen:  now,
	})
	store.Upsert(Resource{
		ID:        "vm2",
		Type:      ResourceTypeVM,
		Name:      "child-vm-2",
		ParentID:  "node1",
		ClusterID: "cluster1",
		LastSeen:  now,
	})
	store.Upsert(Resource{
		ID:        "node2",
		Type:      ResourceTypeNode,
		Name:      "cluster-peer",
		ClusterID: "cluster1",
		LastSeen:  now,
	})

	// Get related resources for vm1
	related := store.GetRelated("vm1")

	// Should have parent
	if parent, ok := related["parent"]; !ok || len(parent) != 1 {
		t.Error("Expected 1 parent")
	}

	// Should have sibling (vm2)
	if siblings, ok := related["siblings"]; !ok || len(siblings) != 1 {
		t.Errorf("Expected 1 sibling, got %d", len(related["siblings"]))
	}

	// Should have cluster members
	if cluster, ok := related["cluster_members"]; !ok || len(cluster) != 3 {
		t.Errorf("Expected 3 cluster members, got %d", len(related["cluster_members"]))
	}
}

func TestGetResourceSummary(t *testing.T) {
	store := NewStore()
	now := time.Now()

	store.Upsert(Resource{
		ID:           "node1",
		Type:         ResourceTypeNode,
		PlatformType: PlatformProxmoxPVE,
		Status:       StatusOnline,
		CPU:          &MetricValue{Current: 50},
		Memory:       &MetricValue{Current: 50}, // 50% usage
		LastSeen:     now,
	})
	store.Upsert(Resource{
		ID:           "vm1",
		Type:         ResourceTypeVM,
		PlatformType: PlatformProxmoxPVE,
		Status:       StatusRunning,
		CPU:          &MetricValue{Current: 70},
		Memory:       &MetricValue{Current: 50}, // 50% usage
		LastSeen:     now,
	})
	store.Upsert(Resource{
		ID:           "vm2",
		Type:         ResourceTypeVM,
		PlatformType: PlatformProxmoxPVE,
		Status:       StatusStopped,
		LastSeen:     now,
	})

	summary := store.GetResourceSummary()

	if summary.TotalResources != 3 {
		t.Errorf("Expected 3 total resources, got %d", summary.TotalResources)
	}
	if summary.Healthy != 2 {
		t.Errorf("Expected 2 healthy, got %d", summary.Healthy)
	}
	if summary.Offline != 1 {
		t.Errorf("Expected 1 offline, got %d", summary.Offline)
	}

	// Check per-type stats
	vmStats := summary.ByType[ResourceTypeVM]
	if vmStats.Count != 2 {
		t.Errorf("Expected 2 VMs, got %d", vmStats.Count)
	}
}

