package store

import (
	"context"
	"errors"
	"testing"
	"time"
)

var listNow = time.Date(2026, 8, 4, 12, 0, 0, 0, time.UTC)

func TestAddListItemDedupesTheOpenList(t *testing.T) {
	ctx := context.Background()
	s := newTestStore(t)

	first, err := s.AddListItem(ctx, ListItem{Item: "молоко", Source: "tap:voice", CreatedTs: listNow})
	if err != nil {
		t.Fatalf("add: %v", err)
	}
	if !first.Created {
		t.Fatal("the first молоко did not create a row")
	}
	again, err := s.AddListItem(ctx, ListItem{Item: " Молоко ", Source: "tap:voice", CreatedTs: listNow})
	if err != nil {
		t.Fatalf("add again: %v", err)
	}
	if again.Created {
		t.Error("молоко was added twice")
	}
	if again.ID != first.ID {
		t.Errorf("second add points at %d; want the existing %d", again.ID, first.ID)
	}
	if _, err := s.AddListItem(ctx, ListItem{Item: "  "}); !errors.Is(err, ErrListItemEmpty) {
		t.Errorf("empty item: %v; want ErrListItemEmpty", err)
	}
}

// A crossed-off item does not block the next one: buying milk again next week
// is a new line, the way saying an errand again is a new task.
func TestCrossedOffItemComesBack(t *testing.T) {
	ctx := context.Background()
	s := newTestStore(t)

	first, err := s.AddListItem(ctx, ListItem{Item: "молоко", CreatedTs: listNow})
	if err != nil {
		t.Fatalf("add: %v", err)
	}
	if err := s.SetListItemStatus(ctx, first.ID, ListItemDone, listNow); err != nil {
		t.Fatalf("cross off: %v", err)
	}
	next, err := s.AddListItem(ctx, ListItem{Item: "молоко", CreatedTs: listNow.Add(time.Hour)})
	if err != nil {
		t.Fatalf("add after: %v", err)
	}
	if !next.Created || next.ID == first.ID {
		t.Errorf("second молоко reused row %d; want a new one", next.ID)
	}
	open, err := s.ListItems(ctx, "", "")
	if err != nil {
		t.Fatalf("list: %v", err)
	}
	if len(open) != 1 || open[0].ID != next.ID {
		t.Errorf("open list %+v; want only the new row", open)
	}
}

// Lists are separate stores under one table: the same word on two lists is two
// items, and reading one never reads the other.
func TestListsDoNotSeeEachOther(t *testing.T) {
	ctx := context.Background()
	s := newTestStore(t)

	if _, err := s.AddListItem(ctx, ListItem{List: "покупки", Item: "вода", CreatedTs: listNow}); err != nil {
		t.Fatalf("add: %v", err)
	}
	if _, err := s.AddListItem(ctx, ListItem{List: "Аптека", Item: "вода", CreatedTs: listNow}); err != nil {
		t.Fatalf("add: %v", err)
	}
	for _, c := range []struct{ list, want string }{
		{"покупки", "покупки"},
		{"аптека", "аптека"},
		{"", "покупки"},
	} {
		got, err := s.ListItems(ctx, c.list, "")
		if err != nil {
			t.Fatalf("list %q: %v", c.list, err)
		}
		if len(got) != 1 {
			t.Fatalf("list %q has %d items; want 1", c.list, len(got))
		}
		if got[0].List != c.want {
			t.Errorf("list %q returned tag %q; want %q", c.list, got[0].List, c.want)
		}
	}
}

func TestClearListCrossesOffEverythingOpen(t *testing.T) {
	ctx := context.Background()
	s := newTestStore(t)

	for _, item := range []string{"молоко", "хлеб", "яйца"} {
		if _, err := s.AddListItem(ctx, ListItem{Item: item, CreatedTs: listNow}); err != nil {
			t.Fatalf("add %s: %v", item, err)
		}
	}
	if _, err := s.AddListItem(ctx, ListItem{List: "аптека", Item: "бинт", CreatedTs: listNow}); err != nil {
		t.Fatalf("add: %v", err)
	}
	n, err := s.ClearList(ctx, "покупки", listNow)
	if err != nil {
		t.Fatalf("clear: %v", err)
	}
	if n != 3 {
		t.Errorf("cleared %d; want 3", n)
	}
	left, err := s.ListItems(ctx, "покупки", "")
	if err != nil {
		t.Fatalf("list: %v", err)
	}
	if len(left) != 0 {
		t.Errorf("%d items still open; want none", len(left))
	}
	done, err := s.ListItems(ctx, "покупки", ListItemDone)
	if err != nil {
		t.Fatalf("list done: %v", err)
	}
	if len(done) != 3 || done[0].ResolvedTs == nil {
		t.Errorf("done list %+v; want 3 rows carrying a resolved time", done)
	}
	other, err := s.ListItems(ctx, "аптека", "")
	if err != nil {
		t.Fatalf("list: %v", err)
	}
	if len(other) != 1 {
		t.Error("clearing покупки touched аптека")
	}
}

func TestSetListItemStatusRejectsWhatIsNotAStatus(t *testing.T) {
	ctx := context.Background()
	s := newTestStore(t)

	if err := s.SetListItemStatus(ctx, 1, "куплено", listNow); !errors.Is(err, ErrListItemStatus) {
		t.Errorf("bad status: %v; want ErrListItemStatus", err)
	}
	if err := s.SetListItemStatus(ctx, 999, ListItemDone, listNow); !errors.Is(err, ErrListItemNotFound) {
		t.Errorf("missing row: %v; want ErrListItemNotFound", err)
	}
}
