package emitter

import (
	"errors"
	"strings"
	"testing"

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

// mockEmitter is a test-only Emitter that returns a fixed File slice
// (or an error) so the Registry tests do not depend on the
// SystemdEmitter. Implements Emitter via value receiver.
type mockEmitter struct {
	files []File
	err   error
}

func (m mockEmitter) Render(spec *jobspec.WorkloadSpec, node *Node) ([]File, error) {
	if m.err != nil {
		return nil, m.err
	}
	out := make([]File, len(m.files))
	copy(out, m.files)
	return out, nil
}

func TestRegistry_RegisterAndRender(t *testing.T) {
	r := NewRegistry()
	want := []File{{Path: "/tmp/a", Content: "alpha", Mode: "0644"}}
	r.Register("job:process", mockEmitter{files: want})
	spec := &jobspec.WorkloadSpec{
		Kind:    "Job",
		Name:    "demo",
		Runtime: &jobspec.RuntimeBlock{OneOf: "process", Command: "/bin/true"},
	}
	node := &Node{Hostname: "node-1", Runtime: []string{"process"}}
	got, err := r.Render(spec, node)
	if err != nil {
		t.Fatalf("Render: %v", err)
	}
	if len(got) != 1 {
		t.Fatalf("got %d files, want 1", len(got))
	}
	if got[0] != want[0] {
		t.Errorf("file = %+v, want %+v", got[0], want[0])
	}
}

func TestRegistry_UnknownKindRuntime(t *testing.T) {
	r := NewRegistry()
	spec := &jobspec.WorkloadSpec{
		Kind:    "Service",
		Name:    "web",
		Runtime: &jobspec.RuntimeBlock{OneOf: "wasm"},
	}
	_, err := r.Render(spec, &Node{})
	if err == nil {
		t.Fatal("expected error for unknown kind:runtime, got nil")
	}
	if !strings.Contains(err.Error(), "no emitter registered") {
		t.Errorf("error = %q, want 'no emitter registered'", err.Error())
	}
	if !strings.Contains(err.Error(), "service:wasm") {
		t.Errorf("error = %q, want it to mention 'service:wasm'", err.Error())
	}
}

func TestRegistry_MultipleEmittersCorrectSelected(t *testing.T) {
	r := NewRegistry()
	jobFiles := []File{{Path: "/tmp/job", Content: "job", Mode: "0644"}}
	svcFiles := []File{{Path: "/tmp/svc", Content: "svc", Mode: "0644"}}
	dsFiles := []File{{Path: "/tmp/ds", Content: "ds", Mode: "0644"}}
	r.Register("job:process", mockEmitter{files: jobFiles})
	r.Register("service:process", mockEmitter{files: svcFiles})
	r.Register("daemonset:process", mockEmitter{files: dsFiles})

	cases := []struct {
		kind     string
		runtime  string
		wantPath string
	}{
		{"Job", "process", "/tmp/job"},
		{"Service", "process", "/tmp/svc"},
		{"DaemonSet", "process", "/tmp/ds"},
	}
	for _, tc := range cases {
		t.Run(tc.kind+":"+tc.runtime, func(t *testing.T) {
			spec := &jobspec.WorkloadSpec{
				Kind:    tc.kind,
				Name:    "x",
				Runtime: &jobspec.RuntimeBlock{OneOf: tc.runtime, Command: "/bin/x"},
			}
			got, err := r.Render(spec, &Node{Hostname: "n"})
			if err != nil {
				t.Fatalf("Render: %v", err)
			}
			if len(got) != 1 {
				t.Fatalf("got %d files, want 1", len(got))
			}
			if got[0].Path != tc.wantPath {
				t.Errorf("path = %q, want %q", got[0].Path, tc.wantPath)
			}
		})
	}
}

func TestRegistry_NilSpec(t *testing.T) {
	r := NewRegistry()
	_, err := r.Render(nil, &Node{})
	if err == nil {
		t.Fatal("expected error for nil spec")
	}
	if !strings.Contains(err.Error(), "spec is nil") {
		t.Errorf("error = %q, want 'spec is nil'", err.Error())
	}
}

func TestRegistry_EmptyKind(t *testing.T) {
	r := NewRegistry()
	spec := &jobspec.WorkloadSpec{Runtime: &jobspec.RuntimeBlock{OneOf: "process"}}
	_, err := r.Render(spec, &Node{})
	if err == nil {
		t.Fatal("expected error for empty kind")
	}
	if !strings.Contains(err.Error(), "kind is empty") {
		t.Errorf("error = %q, want 'kind is empty'", err.Error())
	}
}

func TestRegistry_NilRuntime(t *testing.T) {
	r := NewRegistry()
	spec := &jobspec.WorkloadSpec{Kind: "Job", Name: "x"}
	_, err := r.Render(spec, &Node{})
	if err == nil {
		t.Fatal("expected error for nil runtime")
	}
	if !strings.Contains(err.Error(), "runtime is nil") {
		t.Errorf("error = %q, want 'runtime is nil'", err.Error())
	}
}

func TestRegistry_EmitterErrorPropagates(t *testing.T) {
	r := NewRegistry()
	wantErr := errors.New("boom")
	r.Register("job:process", mockEmitter{err: wantErr})
	spec := &jobspec.WorkloadSpec{
		Kind:    "Job",
		Name:    "x",
		Runtime: &jobspec.RuntimeBlock{OneOf: "process", Command: "/bin/x"},
	}
	_, err := r.Render(spec, &Node{})
	if !errors.Is(err, wantErr) {
		t.Errorf("err = %v, want %v", err, wantErr)
	}
}

// Compile-time assertion that mockEmitter and SystemdEmitter implement
// Emitter.
var (
	_ Emitter = mockEmitter{}
	_ Emitter = SystemdEmitter{}
)
