package notification_test

import (
	"context"
	"errors"
	"log/slog"
	"testing"
	"time"

	"github.com/nomadcode/nomadcode-core/internal/notification"
)

type recordingSink struct {
	events []notification.TaskEvent
	err    error
}

func (s *recordingSink) HandleTaskEvent(_ context.Context, event notification.TaskEvent) error {
	s.events = append(s.events, event)
	return s.err
}

type logRecord struct {
	msg  string
	args map[string]any
}

type spyHandler struct {
	records []logRecord
}

func (h *spyHandler) Enabled(_ context.Context, level slog.Level) bool {
	return true
}

func (h *spyHandler) Handle(_ context.Context, r slog.Record) error {
	args := make(map[string]any)
	r.Attrs(func(a slog.Attr) bool {
		args[a.Key] = a.Value.Any()
		return true
	})
	h.records = append(h.records, logRecord{
		msg:  r.Message,
		args: args,
	})
	return nil
}

func (h *spyHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
	return h
}

func (h *spyHandler) WithGroup(name string) slog.Handler {
	return h
}

func TestNotifyTaskCompletedUsesTaskEventCompleted(t *testing.T) {
	spy := &spyHandler{}
	logger := slog.New(spy)

	service := notification.NewService(logger)

	err := service.NotifyTaskCompleted(context.Background(), notification.TaskNotification{
		TaskID:  "task-123",
		Title:   "Test Task",
		Status:  "completed",
		Message: "Finished successfully",
	})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if len(spy.records) == 0 {
		t.Fatal("expected log records, got none")
	}

	found := false
	for _, rec := range spy.records {
		if rec.msg == "task event notification requested" {
			found = true
			if rec.args["type"] != notification.TaskEventCompleted {
				t.Errorf("expected event type %s, got %v", notification.TaskEventCompleted, rec.args["type"])
			}
			if rec.args["task_id"] != "task-123" {
				t.Errorf("expected task_id task-123, got %v", rec.args["task_id"])
			}
		}
	}
	if !found {
		t.Error("expected 'task event notification requested' log message, not found")
	}
}

func TestNotifyTaskEventFansOutToSinksForRunningAndFailed(t *testing.T) {
	logger := slog.New(&spyHandler{})
	sink := &recordingSink{}
	service := notification.NewService(logger, sink)

	now := time.Now().UTC()
	events := []notification.TaskEvent{
		{
			Type:       notification.TaskEventRunning,
			TaskID:     "task-1",
			Status:     "running",
			Attempt:    1,
			Reason:     "first-try",
			OccurredAt: now,
		},
		{
			Type:       notification.TaskEventFailed,
			TaskID:     "task-2",
			Status:     "failed",
			Attempt:    2,
			Reason:     "execution-error",
			OccurredAt: now,
		},
		{
			Type:       notification.TaskEventCompleted,
			TaskID:     "task-3",
			Status:     "completed",
			Attempt:    3,
			Reason:     "success-after-retries",
			OccurredAt: now,
		},
	}
	for _, e := range events {
		if err := service.NotifyTaskEvent(context.Background(), e); err != nil {
			t.Fatalf("unexpected error for %s: %v", e.Type, err)
		}
	}

	if len(sink.events) != 3 {
		t.Fatalf("expected sink to receive 3 events, got %d", len(sink.events))
	}
	if sink.events[0].Type != notification.TaskEventRunning || sink.events[1].Type != notification.TaskEventFailed {
		t.Errorf("sink did not receive non-completed events: %+v", sink.events)
	}
	if sink.events[2].Type != notification.TaskEventCompleted {
		t.Errorf("sink did not receive completed event: %+v", sink.events)
	}

	// Assert additive fields are preserved and fanned out
	for i, e := range events {
		got := sink.events[i]
		if got.Attempt != e.Attempt {
			t.Errorf("event %d: expected attempt %d, got %d", i, e.Attempt, got.Attempt)
		}
		if got.Reason != e.Reason {
			t.Errorf("event %d: expected reason %q, got %q", i, e.Reason, got.Reason)
		}
		if !got.OccurredAt.Equal(e.OccurredAt) {
			t.Errorf("event %d: expected occurred_at %v, got %v", i, e.OccurredAt, got.OccurredAt)
		}
	}
}

func TestNotifyTaskEventReturnsSinkErrors(t *testing.T) {
	logger := slog.New(&spyHandler{})
	sinkErr := errors.New("sink boom")
	sink := &recordingSink{err: sinkErr}
	service := notification.NewService(logger, sink)

	err := service.NotifyTaskEvent(context.Background(), notification.TaskEvent{
		Type:   notification.TaskEventRunning,
		TaskID: "task-1",
		Status: "running",
	})
	if err == nil {
		t.Fatal("expected sink error to be returned")
	}
	if !errors.Is(err, sinkErr) {
		t.Errorf("expected joined error to wrap sink error, got %v", err)
	}
}

func TestNotifyTaskEventWorksWithoutSinks(t *testing.T) {
	spy := &spyHandler{}
	logger := slog.New(spy)

	service := notification.NewService(logger)

	err := service.NotifyTaskEvent(context.Background(), notification.TaskEvent{
		Type:    notification.TaskEventFailed,
		TaskID:  "task-456",
		Title:   "Failed Task",
		Status:  "failed",
		Message: "Something went wrong",
	})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	found := false
	for _, rec := range spy.records {
		if rec.msg == "task event notification requested" {
			found = true
			if rec.args["type"] != notification.TaskEventFailed {
				t.Errorf("expected event type %s, got %v", notification.TaskEventFailed, rec.args["type"])
			}
			if rec.args["task_id"] != "task-456" {
				t.Errorf("expected task_id task-456, got %v", rec.args["task_id"])
			}
		}
	}
	if !found {
		t.Error("expected 'task event notification requested' log message, not found")
	}
}
