package notifications

import (
	"context"
	"testing"
	"time"

	"switchboard-core/models"
)

// mockNotifStore implements store.NotificationStore for testing.
type mockNotifStore struct {
	items []models.Notification
}

func (m *mockNotifStore) Create(_ context.Context, n *models.Notification) error {
	n.ID = "mock-id"
	n.CreatedAt = time.Now()
	m.items = append(m.items, *n)
	return nil
}

func (m *mockNotifStore) ListByUser(_ context.Context, _ string, _, _ int, _ bool) ([]models.Notification, int, error) {
	return m.items, len(m.items), nil
}
func (m *mockNotifStore) MarkRead(_ context.Context, _, _ string) error { return nil }
func (m *mockNotifStore) MarkAllRead(_ context.Context, _ string) error { return nil }
func (m *mockNotifStore) Delete(_ context.Context, _, _ string) error   { return nil }
func (m *mockNotifStore) UnreadCount(_ context.Context, _ string) (int, error) {
	return len(m.items), nil
}
func (m *mockNotifStore) DeleteOlderThan(_ context.Context, _ time.Time) (int64, error) {
	return 0, nil
}

// newTestService creates a Service with a mock store for testing sources.
func newTestService() (*Service, *mockNotifStore) {
	ms := &mockNotifStore{}
	svc := NewService(ms, nil) // no hub
	return svc, ms
}

// ── NotifyKBReady ─────────────────────────

func TestNotifyKBReady(t *testing.T) {
	svc, ms := newTestService()

	NotifyKBReady(svc, "user1", "kb1", "My KB", 42)

	if len(ms.items) != 1 {
		t.Fatalf("expected 1 notification, got %d", len(ms.items))
	}
	n := ms.items[0]
	if n.UserID != "user1" {
		t.Errorf("user_id: got %q, want %q", n.UserID, "user1")
	}
	if n.Type != models.NotifTypeKBReady {
		t.Errorf("type: got %q, want %q", n.Type, models.NotifTypeKBReady)
	}
	if n.ResourceType != models.ResourceTypeKnowledgeBase {
		t.Errorf("resource_type: got %q, want %q", n.ResourceType, models.ResourceTypeKnowledgeBase)
	}
	if n.ResourceID != "kb1" {
		t.Errorf("resource_id: got %q, want %q", n.ResourceID, "kb1")
	}
}

func TestNotifyKBReady_NilService(t *testing.T) {
	// Should not panic
	NotifyKBReady(nil, "user1", "kb1", "My KB", 10)
}

// ── NotifyKBError ─────────────────────────

func TestNotifyKBError(t *testing.T) {
	svc, ms := newTestService()

	NotifyKBError(svc, "user1", "kb2", "Bad KB", "chunk failed")

	if len(ms.items) != 1 {
		t.Fatalf("expected 1 notification, got %d", len(ms.items))
	}
	n := ms.items[0]
	if n.Type != models.NotifTypeKBError {
		t.Errorf("type: got %q, want %q", n.Type, models.NotifTypeKBError)
	}
	if n.Body != "chunk failed" {
		t.Errorf("body: got %q, want %q", n.Body, "chunk failed")
	}
}

// ── NotifyGroupMemberAdded ────────────────

func TestNotifyGroupMemberAdded(t *testing.T) {
	svc, ms := newTestService()

	NotifyGroupMemberAdded(svc, "user1", "grp1", "Engineering")

	if len(ms.items) != 1 {
		t.Fatalf("expected 1 notification, got %d", len(ms.items))
	}
	n := ms.items[0]
	if n.Type != models.NotifTypeGrantChanged {
		t.Errorf("type: got %q, want %q", n.Type, models.NotifTypeGrantChanged)
	}
	if n.ResourceType != models.ResourceTypeGroup {
		t.Errorf("resource_type: got %q, want %q", n.ResourceType, models.ResourceTypeGroup)
	}
	if n.ResourceID != "grp1" {
		t.Errorf("resource_id: got %q, want %q", n.ResourceID, "grp1")
	}
}

// ── NotifyGroupMemberRemoved ──────────────

func TestNotifyGroupMemberRemoved(t *testing.T) {
	svc, ms := newTestService()

	NotifyGroupMemberRemoved(svc, "user1", "grp1", "Engineering")

	if len(ms.items) != 1 {
		t.Fatalf("expected 1 notification, got %d", len(ms.items))
	}
	n := ms.items[0]
	if n.Type != models.NotifTypeGrantChanged {
		t.Errorf("type: got %q", n.Type)
	}
}

// ── NotifyUserMentioned ───────────────────

func TestNotifyUserMentioned(t *testing.T) {
	svc, ms := newTestService()

	NotifyUserMentioned(svc, "mentioned-user", "chan1", "from-user", "hey @you check this")

	if len(ms.items) != 1 {
		t.Fatalf("expected 1 notification, got %d", len(ms.items))
	}
	n := ms.items[0]
	if n.UserID != "mentioned-user" {
		t.Errorf("user_id: got %q", n.UserID)
	}
	if n.Type != models.NotifTypeUserMentioned {
		t.Errorf("type: got %q, want %q", n.Type, models.NotifTypeUserMentioned)
	}
	if n.Body != "hey @you check this" {
		t.Errorf("body: got %q", n.Body)
	}
	if n.ResourceType != models.ResourceTypeChannel {
		t.Errorf("resource_type: got %q", n.ResourceType)
	}
	if n.ResourceID != "chan1" {
		t.Errorf("resource_id: got %q", n.ResourceID)
	}
}

func TestNotifyUserMentioned_NilService(t *testing.T) {
	NotifyUserMentioned(nil, "u1", "c1", "u2", "hello") // should not panic
}

// ── NotifyWorkflowClaimed ─────────────────

func TestNotifyWorkflowClaimed(t *testing.T) {
	svc, ms := newTestService()

	NotifyWorkflowClaimed(svc, "claimer1", "assign1", "chan1")

	if len(ms.items) != 1 {
		t.Fatalf("expected 1 notification, got %d", len(ms.items))
	}
	n := ms.items[0]
	if n.UserID != "claimer1" {
		t.Errorf("user_id: got %q", n.UserID)
	}
	if n.Type != models.NotifTypeWorkflowClaimed {
		t.Errorf("type: got %q, want %q", n.Type, models.NotifTypeWorkflowClaimed)
	}
	if n.ResourceType != models.ResourceTypeChannel {
		t.Errorf("resource_type: got %q", n.ResourceType)
	}
	if n.ResourceID != "chan1" {
		t.Errorf("resource_id: got %q", n.ResourceID)
	}
}

func TestNotifyWorkflowClaimed_NilService(t *testing.T) {
	NotifyWorkflowClaimed(nil, "u1", "a1", "c1") // should not panic
}

// ── NotifyMany ────────────────────────────

func TestNotifyMany(t *testing.T) {
	svc, ms := newTestService()

	template := models.Notification{
		Type:  "test.broadcast",
		Title: "Hello everyone",
	}

	svc.NotifyMany(context.Background(), []string{"u1", "u2", "u3"}, template)

	if len(ms.items) != 3 {
		t.Fatalf("expected 3 notifications, got %d", len(ms.items))
	}

	seen := map[string]bool{}
	for _, n := range ms.items {
		seen[n.UserID] = true
		if n.Type != "test.broadcast" {
			t.Errorf("type: got %q", n.Type)
		}
	}
	for _, uid := range []string{"u1", "u2", "u3"} {
		if !seen[uid] {
			t.Errorf("missing notification for %s", uid)
		}
	}
}

// ── Email subject lines for new types ─────

func TestSubjectForNotification_NewTypes(t *testing.T) {
	tests := []struct {
		notifType string
		title     string
		wantPfx   string
	}{
		{"memory.extracted", "3 new memories", "[Test] "},
		{"user.mentioned", "You were mentioned", "[Test] "},
		{"workflow.claimed", "Assignment claimed", "[Test] "},
		{"task.completed", "Task done", "[Test] "},
		{"task.budget_exceeded", "Budget hit", "[Test] "},
	}
	for _, tt := range tests {
		n := &models.Notification{Type: tt.notifType, Title: tt.title}
		got := SubjectForNotification(n, "Test")
		if got == "" {
			t.Errorf("SubjectForNotification(%q) returned empty", tt.notifType)
		}
		// All should at least have the instance prefix
		if len(got) < len(tt.wantPfx) {
			t.Errorf("SubjectForNotification(%q) too short: %q", tt.notifType, got)
		}
	}
}
