// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

package event_test

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

	"git.secluded.site/np/internal/event"
	"git.secluded.site/np/internal/goal"
	"git.secluded.site/np/internal/task"
	"git.secluded.site/np/internal/testutil"
)

func TestStoreAppendAndList(t *testing.T) {
	ctx := context.Background()
	db := testutil.OpenDB(t)

	clock := &testutil.SequenceClock{
		Times: []time.Time{
			time.Date(2025, time.March, 3, 10, 15, 0, 0, time.FixedZone("A", 3600)),
			time.Date(2025, time.March, 3, 10, 20, 0, 0, time.FixedZone("B", -3600)),
		},
	}

	store := event.NewStore(db, clock)
	sid := "sid-events"

	goalDoc := goal.Document{
		Title:       "Plan",
		Description: "do something",
		UpdatedAt:   time.Date(2025, time.March, 3, 9, 0, 0, 0, time.FixedZone("C", 7200)),
	}

	first, err := store.Append(ctx, sid, event.BuildGoalSet("np g s", " reason ", goalDoc))
	if err != nil {
		t.Fatalf("Append goal_set: %v", err)
	}
	if first.Seq != 1 {
		t.Fatalf("Expected seq 1, got %d", first.Seq)
	}
	if !first.At.Equal(clock.Times[0].UTC()) {
		t.Fatalf("At mismatch: want %v got %v", clock.Times[0].UTC(), first.At)
	}
	if !first.HasReason() {
		t.Fatalf("Expected reason")
	}
	if want := "reason"; *first.Reason != want {
		t.Fatalf("Reason mismatch: want %q got %q", want, *first.Reason)
	}
	var goalPayload event.GoalSetPayload
	if err := first.UnmarshalPayload(&goalPayload); err != nil {
		t.Fatalf("UnmarshalPayload goal_set: %v", err)
	}
	if want := event.GoalSnapshotFrom(goalDoc); goalPayload.Goal != want {
		t.Fatalf("Goal payload mismatch: want %+v got %+v", want, goalPayload.Goal)
	}

	taskAdded := task.Task{
		ID:          "abc123",
		Title:       "Task",
		Description: "finish work",
		Status:      task.StatusPending,
		CreatedAt:   time.Date(2025, time.March, 3, 9, 30, 0, 0, time.UTC),
		UpdatedAt:   time.Date(2025, time.March, 3, 9, 30, 0, 0, time.UTC),
		CreatedSeq:  1,
	}

	second, err := store.Append(ctx, sid, event.BuildTaskAdded("np t a", "", taskAdded))
	if err != nil {
		t.Fatalf("Append task_added: %v", err)
	}
	if second.Seq != 2 {
		t.Fatalf("Expected seq 2, got %d", second.Seq)
	}
	if second.Reason != nil {
		t.Fatalf("Expected nil reason")
	}
	if !second.At.Equal(clock.Times[1].UTC()) {
		t.Fatalf("Second At mismatch: want %v got %v", clock.Times[1].UTC(), second.At)
	}

	var taskPayload event.TaskAddedPayload
	if err := second.UnmarshalPayload(&taskPayload); err != nil {
		t.Fatalf("UnmarshalPayload task_added: %v", err)
	}
	if taskPayload.Task != taskAdded {
		t.Fatalf("Task payload mismatch: want %+v got %+v", taskAdded, taskPayload.Task)
	}

	all, err := store.List(ctx, sid, event.ListOptions{})
	if err != nil {
		t.Fatalf("List: %v", err)
	}
	if len(all) != 2 {
		t.Fatalf("Expected 2 events, got %d", len(all))
	}
	if all[0].Seq != 1 || all[1].Seq != 2 {
		t.Fatalf("Events not ordered: %+v", all)
	}

	after, err := store.List(ctx, sid, event.ListOptions{After: 1})
	if err != nil {
		t.Fatalf("List after=1: %v", err)
	}
	if len(after) != 1 || after[0].Seq != 2 {
		t.Fatalf("After list mismatch: %+v", after)
	}

	limited, err := store.List(ctx, sid, event.ListOptions{Limit: 1})
	if err != nil {
		t.Fatalf("List limit=1: %v", err)
	}
	if len(limited) != 1 || limited[0].Seq != 1 {
		t.Fatalf("Limited list mismatch: %+v", limited)
	}

	seq, err := store.LatestSequence(ctx, sid)
	if err != nil {
		t.Fatalf("LatestSequence: %v", err)
	}
	if seq != 2 {
		t.Fatalf("LatestSequence mismatch: want 2 got %d", seq)
	}

	emptySeq, err := store.LatestSequence(ctx, "missing")
	if err != nil {
		t.Fatalf("LatestSequence missing: %v", err)
	}
	if emptySeq != 0 {
		t.Fatalf("LatestSequence should be 0 for missing, got %d", emptySeq)
	}
}

func TestAppendValidation(t *testing.T) {
	ctx := context.Background()
	db := testutil.OpenDB(t)
	store := event.NewStore(db, &testutil.SequenceClock{Times: []time.Time{time.Now()}})

	if _, err := store.Append(ctx, "sid", event.AppendInput{Type: "unknown", Command: "cmd"}); !errors.Is(err, event.ErrInvalidType) {
		t.Fatalf("expected ErrInvalidType, got %v", err)
	}

	if _, err := store.Append(ctx, "sid", event.AppendInput{Type: event.TypeGoalSet, Command: "   "}); !errors.Is(err, event.ErrEmptyCommand) {
		t.Fatalf("expected ErrEmptyCommand, got %v", err)
	}
}
