package emitter

import (
	"strings"
	"testing"

	"git.cloudinit.dev/coreci/orca/internal/jobspec"
)

func TestSyncthingEmitter_TwoReplicatedVolumes_TwoFiles(t *testing.T) {
	spec := &jobspec.WorkloadSpec{
		Kind: "Service",
		Name: "team-alpha",
		Volumes: []jobspec.VolumeSpec{
			{Name: "data", Source: "replicate:peer-b,peer-c", Target: "/var/lib/orca/data"},
			{Name: "logs", Source: "replicate:peer-b", Target: "/var/lib/orca/logs"},
			{Name: "cache", Source: "/local/cache", Target: "/cache"}, // not replicated
		},
	}
	node := &Node{Hostname: "peer-a", Runtime: []string{"process"}}
	files, err := (SyncthingEmitter{}).Render(spec, node)
	if err != nil {
		t.Fatalf("Render: %v", err)
	}
	if len(files) != 2 {
		t.Fatalf("expected 2 files (only replicated volumes), got %d", len(files))
	}
	for _, f := range files {
		if !strings.HasPrefix(f.Path, "/etc/syncthing/orca-team-alpha-") {
			t.Errorf("path %q does not start with /etc/syncthing/orca-team-alpha-", f.Path)
		}
		if !strings.HasSuffix(f.Path, ".xml") {
			t.Errorf("path %q does not end with .xml", f.Path)
		}
		if f.Mode != "0644" {
			t.Errorf("mode = %q, want 0644", f.Mode)
		}
		if !strings.Contains(f.Content, "<configuration") {
			t.Errorf("content of %q is not syncthing config XML", f.Path)
		}
	}
	// File 1: data volume, replicated to peer-b and peer-c.
	if !strings.Contains(files[0].Path, "team-alpha-data") {
		t.Errorf("first file path = %q, want team-alpha-data", files[0].Path)
	}
	if !strings.Contains(files[0].Content, "peer-b") || !strings.Contains(files[0].Content, "peer-c") {
		t.Errorf("data volume config missing peer-b or peer-c")
	}
	// File 2: logs volume, replicated to peer-b only.
	if !strings.Contains(files[1].Path, "team-alpha-logs") {
		t.Errorf("second file path = %q, want team-alpha-logs", files[1].Path)
	}
	if !strings.Contains(files[1].Content, "peer-b") {
		t.Errorf("logs volume config missing peer-b")
	}
	if strings.Contains(files[1].Content, "tcp://peer-c") {
		t.Errorf("logs volume config should not contain peer-c")
	}
}

func TestSyncthingEmitter_NoReplicatedVolumes_Empty(t *testing.T) {
	spec := &jobspec.WorkloadSpec{
		Kind: "Job",
		Name: "batch",
		Volumes: []jobspec.VolumeSpec{
			{Name: "cache", Source: "/local/cache", Target: "/cache"},
		},
	}
	node := &Node{Hostname: "peer-a"}
	files, err := (SyncthingEmitter{}).Render(spec, node)
	if err != nil {
		t.Fatalf("Render: %v", err)
	}
	if len(files) != 0 {
		t.Errorf("expected 0 files for non-replicated volumes, got %d", len(files))
	}
}

func TestSyncthingEmitter_NoVolumes_Empty(t *testing.T) {
	spec := &jobspec.WorkloadSpec{Kind: "Job", Name: "batch"}
	node := &Node{Hostname: "peer-a"}
	files, err := (SyncthingEmitter{}).Render(spec, node)
	if err != nil {
		t.Fatalf("Render: %v", err)
	}
	if len(files) != 0 {
		t.Errorf("expected 0 files, got %d", len(files))
	}
}

func TestSyncthingEmitter_NilSpec_Error(t *testing.T) {
	node := &Node{Hostname: "peer-a"}
	if _, err := (SyncthingEmitter{}).Render(nil, node); err == nil {
		t.Error("nil spec: expected error")
	}
}

func TestSyncthingEmitter_NilNode_Error(t *testing.T) {
	spec := &jobspec.WorkloadSpec{Kind: "Job", Name: "x"}
	if _, err := (SyncthingEmitter{}).Render(spec, nil); err == nil {
		t.Error("nil node: expected error")
	}
}

func TestSyncthingEmitter_LocalDeviceFirst(t *testing.T) {
	spec := &jobspec.WorkloadSpec{
		Kind:    "Service",
		Name:    "ns",
		Volumes: []jobspec.VolumeSpec{{Name: "data", Source: "replicate:peer-b", Target: "/data"}},
	}
	node := &Node{Hostname: "peer-a"}
	files, err := (SyncthingEmitter{}).Render(spec, node)
	if err != nil {
		t.Fatalf("Render: %v", err)
	}
	if len(files) != 1 {
		t.Fatalf("expected 1 file, got %d", len(files))
	}
	// Local peer address is "dynamic"; remote peer uses tcp://...
	if !strings.Contains(files[0].Content, "dynamic") {
		t.Errorf("local device address (dynamic) missing from config")
	}
	if !strings.Contains(files[0].Content, "tcp://peer-b:22000") {
		t.Errorf("remote peer address missing from config")
	}
}

func TestParseReplicateList_OK(t *testing.T) {
	peers, ok := parseReplicateList("replicate:peer-b,peer-c,peer-d")
	if !ok {
		t.Fatal("expected ok")
	}
	if len(peers) != 3 || peers[0] != "peer-b" || peers[1] != "peer-c" || peers[2] != "peer-d" {
		t.Errorf("peers = %v, want [peer-b peer-c peer-d]", peers)
	}
}

func TestParseReplicateList_NotReplicated(t *testing.T) {
	_, ok := parseReplicateList("/local/path")
	if ok {
		t.Error("non-replicate source should return ok=false")
	}
}

func TestParseReplicateList_EmptyPeerList(t *testing.T) {
	peers, ok := parseReplicateList("replicate:")
	if !ok {
		t.Error("replicate: prefix should return ok=true")
	}
	if len(peers) != 0 {
		t.Errorf("peers = %v, want empty", peers)
	}
}

func TestSyncthingDeviceID_Stable(t *testing.T) {
	a := syncthingDeviceID("peer-a")
	b := syncthingDeviceID("peer-a")
	if a != b {
		t.Errorf("syncthingDeviceID not stable: %q vs %q", a, b)
	}
	if len(a) != 52 {
		t.Errorf("device ID length = %d, want 52", len(a))
	}
}

func TestSyncthingDeviceID_DifferentPeers(t *testing.T) {
	a := syncthingDeviceID("peer-a")
	b := syncthingDeviceID("peer-b")
	if a == b {
		t.Errorf("different peers produced same device ID")
	}
}
