package main

import (
	"reflect"
	"sort"
	"testing"
)

func TestExtractMentions(t *testing.T) {
	cases := []struct {
		in   string
		want []string
	}{
		{"hola @alice", []string{"alice"}},
		{"@Bob y @bob mismo", []string{"bob"}},
		{"sin menciones", nil},
		{"email@foo.com no cuenta como @real_user", []string{"foo.com", "real_user"}},
		{"@a-b-c y @d.e", []string{"a-b-c", "d.e"}},
	}
	for _, c := range cases {
		got := extractMentions(c.in)
		sort.Strings(got)
		sort.Strings(c.want)
		if !reflect.DeepEqual(got, c.want) {
			t.Errorf("extractMentions(%q) = %v, want %v", c.in, got, c.want)
		}
	}
}

func mkUser(t *testing.T, db *DB, username string) string {
	t.Helper()
	u, err := db.CreateUser(username, "passw", username)
	if err != nil {
		t.Fatalf("CreateUser %q: %v", username, err)
	}
	return u.ID
}

func mkCard(t *testing.T, db *DB, columnID, requester, title, assigneeID string) string {
	t.Helper()
	c, err := db.CreateCard(columnID, requester, title, "", "")
	if err != nil {
		t.Fatalf("CreateCard: %v", err)
	}
	if assigneeID != "" {
		if err := db.UpdateCardWithActor(c.ID, CardPatch{AssigneeID: &assigneeID, HasAssignee: true}, ""); err != nil {
			t.Fatalf("assign: %v", err)
		}
	}
	return c.ID
}

func TestCreateCardMessageAndNotify_AssigneeAndPreviousAuthors(t *testing.T) {
	db := setupTestDB(t)

	alice := mkUser(t, db, "alice")
	bob := mkUser(t, db, "bob")
	carol := mkUser(t, db, "carol")

	col, err := db.CreateColumn("Todo")
	if err != nil {
		t.Fatalf("CreateColumn: %v", err)
	}
	card := mkCard(t, db, col.ID, "x", "card", bob)

	// 1) alice writes; bob is assignee → bob gets assigned_chat.
	_, notifs, _, err := db.CreateCardMessageAndNotify(card, alice, "hola", nil)
	if err != nil {
		t.Fatalf("create msg: %v", err)
	}
	if len(notifs) != 1 || notifs[0].UserID != bob || notifs[0].Kind != NotifKindAssignedChat {
		t.Fatalf("expected single assigned_chat for bob, got %+v", notifs)
	}

	// 2) carol replies (carol is neither assignee nor previous author).
	//    alice (previous author) gets reply; bob (assignee) gets assigned_chat.
	_, notifs, _, err = db.CreateCardMessageAndNotify(card, carol, "hola alice", nil)
	if err != nil {
		t.Fatalf("create msg: %v", err)
	}
	gotKinds := map[string]string{}
	for _, n := range notifs {
		gotKinds[n.UserID] = n.Kind
	}
	wantKinds := map[string]string{alice: NotifKindReply, bob: NotifKindAssignedChat}
	if !reflect.DeepEqual(gotKinds, wantKinds) {
		t.Fatalf("kinds = %+v, want %+v", gotKinds, wantKinds)
	}
}

func TestCreateCardMessageAndNotify_MentionsBeatOtherKinds(t *testing.T) {
	db := setupTestDB(t)
	alice := mkUser(t, db, "alice")
	bob := mkUser(t, db, "bob")
	col, _ := db.CreateColumn("Todo")
	card := mkCard(t, db, col.ID, "x", "card", bob) // bob is assignee

	// alice mentions bob explicitly → kind must be 'mention', not 'assigned_chat'.
	_, notifs, mentions, err := db.CreateCardMessageAndNotify(card, alice, "oye @bob mira esto", nil)
	if err != nil {
		t.Fatalf("create: %v", err)
	}
	if len(mentions) != 1 || mentions[0].UserID != bob {
		t.Fatalf("mentions = %+v, want [bob]", mentions)
	}
	if len(notifs) != 1 || notifs[0].UserID != bob || notifs[0].Kind != NotifKindMention {
		t.Fatalf("notifs = %+v, want single mention for bob", notifs)
	}
}

func TestCreateCardMessageAndNotify_UnknownMentionsIgnored(t *testing.T) {
	db := setupTestDB(t)
	alice := mkUser(t, db, "alice")
	col, _ := db.CreateColumn("Todo")
	card := mkCard(t, db, col.ID, "x", "card", "")

	_, notifs, mentions, err := db.CreateCardMessageAndNotify(card, alice, "hola @noexiste", nil)
	if err != nil {
		t.Fatalf("create: %v", err)
	}
	if len(mentions) != 0 || len(notifs) != 0 {
		t.Fatalf("got mentions=%v notifs=%v, want empty", mentions, notifs)
	}
}

func TestCreateCardMessageAndNotify_AuthorNeverSelfNotified(t *testing.T) {
	db := setupTestDB(t)
	alice := mkUser(t, db, "alice")
	col, _ := db.CreateColumn("Todo")
	card := mkCard(t, db, col.ID, "x", "card", alice) // alice is assignee

	// alice mentions herself + is assignee → no notification.
	_, notifs, _, err := db.CreateCardMessageAndNotify(card, alice, "monologo @alice", nil)
	if err != nil {
		t.Fatalf("create: %v", err)
	}
	if len(notifs) != 0 {
		t.Fatalf("notifs = %+v, want empty (self)", notifs)
	}
}

func TestListAndMarkRead(t *testing.T) {
	db := setupTestDB(t)
	alice := mkUser(t, db, "alice")
	bob := mkUser(t, db, "bob")
	col, _ := db.CreateColumn("Todo")
	card := mkCard(t, db, col.ID, "x", "card", bob)

	_, _, _, _ = db.CreateCardMessageAndNotify(card, alice, "1", nil)
	_, _, _, _ = db.CreateCardMessageAndNotify(card, alice, "2", nil)

	got, err := db.ListNotifications(bob, true, 0)
	if err != nil {
		t.Fatalf("list: %v", err)
	}
	if len(got) != 2 {
		t.Fatalf("len = %d, want 2", len(got))
	}
	if n, _ := db.CountUnreadNotifications(bob); n != 2 {
		t.Fatalf("unread count = %d, want 2", n)
	}

	if err := db.MarkNotificationRead(bob, got[0].ID); err != nil {
		t.Fatalf("mark read: %v", err)
	}
	if n, _ := db.CountUnreadNotifications(bob); n != 1 {
		t.Fatalf("unread count after mark = %d, want 1", n)
	}
	// idempotent
	if err := db.MarkNotificationRead(bob, got[0].ID); err != nil {
		t.Fatalf("mark read 2nd time: %v", err)
	}
	if n, _ := db.MarkAllNotificationsRead(bob); n != 1 {
		t.Fatalf("mark all = %d, want 1", n)
	}
	if n, _ := db.CountUnreadNotifications(bob); n != 0 {
		t.Fatalf("unread count after mark-all = %d, want 0", n)
	}
}
