package collaboration

import (
	"context"
	"database/sql"
	"io"
	"log/slog"
	"path/filepath"
	"testing"
	"time"

	"github.com/tim/cairnquire/apps/server/internal/auth"
	"github.com/tim/cairnquire/apps/server/internal/config"
	"github.com/tim/cairnquire/apps/server/internal/database"
)

func newCollaborationTestRepository(t *testing.T) (*Repository, *sql.DB) {
	t.Helper()
	ctx := context.Background()
	db, err := database.Open(ctx, config.DatabaseConfig{Path: filepath.Join(t.TempDir(), "test.sqlite")})
	if err != nil {
		t.Fatalf("open database: %v", err)
	}
	t.Cleanup(func() { _ = db.Close() })
	if err := database.ApplyMigrations(ctx, db.SQL()); err != nil {
		t.Fatalf("apply migrations: %v", err)
	}
	now := formatTime(time.Now().UTC())
	for _, user := range []struct{ id, email, name string }{
		{"user:author", "author@example.com", "Author"},
		{"user:watcher", "watcher@example.com", "Watcher"},
	} {
		if _, err := db.SQL().ExecContext(ctx, `
			INSERT INTO users (id, email, display_name, role, created_at) VALUES (?, ?, ?, 'editor', ?)
		`, user.id, user.email, user.name, now); err != nil {
			t.Fatalf("insert user: %v", err)
		}
	}
	for _, document := range []struct{ id, path, title string }{
		{"doc:guide/setup", "guide/setup.md", "Setup"},
		{"doc:other", "other.md", "Other"},
	} {
		if _, err := db.SQL().ExecContext(ctx, `
			INSERT INTO documents (id, path, current_hash, title, tags, created_at, updated_at)
			VALUES (?, ?, 'hash', ?, '[]', ?, ?)
		`, document.id, document.path, document.title, now, now); err != nil {
			t.Fatalf("insert document: %v", err)
		}
	}
	return NewRepository(db.SQL()), db.SQL()
}

func TestWatcherTargetsAreIdempotentAndFolderMatchingUsesAncestors(t *testing.T) {
	repo, db := newCollaborationTestRepository(t)
	ctx := context.Background()
	now := time.Now().UTC()
	documentID := "doc:guide/setup"
	folder := "guide"
	for range 2 {
		if err := repo.AddWatcher(ctx, Watcher{UserID: "user:watcher", DocumentID: &documentID, CreatedAt: now}); err != nil {
			t.Fatalf("add document watcher: %v", err)
		}
		if err := repo.AddWatcher(ctx, Watcher{UserID: "user:watcher", FolderPath: &folder, CreatedAt: now}); err != nil {
			t.Fatalf("add folder watcher: %v", err)
		}
	}
	var count int
	if err := db.QueryRowContext(ctx, `SELECT COUNT(*) FROM watchers`).Scan(&count); err != nil {
		t.Fatalf("count watchers: %v", err)
	}
	if count != 2 {
		t.Fatalf("watcher count = %d, want 2", count)
	}

	tests := []struct {
		path string
		want int
	}{
		{"guide/setup.md", 1},
		{"guide/deep/setup.md", 1},
		{"guidebook/setup.md", 0},
		{"other.md", 0},
	}
	for _, test := range tests {
		watchers, err := repo.ListWatchersByFolder(ctx, test.path)
		if err != nil {
			t.Fatalf("ListWatchersByFolder(%q): %v", test.path, err)
		}
		if len(watchers) != test.want {
			t.Errorf("ListWatchersByFolder(%q) = %d, want %d", test.path, len(watchers), test.want)
		}
	}
}

func TestCommentReplyValidationHistoryAndThreadResolution(t *testing.T) {
	repo, _ := newCollaborationTestRepository(t)
	logger := slog.New(slog.NewTextHandler(io.Discard, nil))
	service := NewServiceWithOptions(repo, nil, logger, Options{})
	ctx := context.Background()
	principal := auth.Principal{UserID: "user:author", DisplayName: "Author"}
	anchor := "anchor"

	root, err := service.CreateComment(ctx, principal, CreateCommentInput{
		DocumentID: "doc:guide/setup", VersionHash: "hash", Content: "Root", AnchorHash: &anchor,
	})
	if err != nil {
		t.Fatalf("create root: %v", err)
	}
	reply, err := service.CreateComment(ctx, principal, CreateCommentInput{
		DocumentID: "doc:guide/setup", VersionHash: "hash", Content: "Reply", ParentID: &root.ID,
	})
	if err != nil {
		t.Fatalf("create reply: %v", err)
	}
	if reply.AnchorHash == nil || *reply.AnchorHash != anchor {
		t.Fatalf("reply anchor = %#v, want inherited %q", reply.AnchorHash, anchor)
	}
	grandchild, err := service.CreateComment(ctx, principal, CreateCommentInput{
		DocumentID: "doc:guide/setup", VersionHash: "hash", Content: "Grandchild", ParentID: &reply.ID,
	})
	if err != nil {
		t.Fatalf("create grandchild: %v", err)
	}
	if _, err := service.CreateComment(ctx, principal, CreateCommentInput{
		DocumentID: "doc:other", VersionHash: "hash", Content: "Wrong document", ParentID: &root.ID,
	}); err == nil {
		t.Fatal("cross-document reply should fail")
	}

	if err := service.ResolveComment(ctx, principal, root.ID); err != nil {
		t.Fatalf("resolve thread: %v", err)
	}
	active, err := service.ListCommentsByAnchor(ctx, "doc:guide/setup", anchor, false)
	if err != nil {
		t.Fatalf("list active: %v", err)
	}
	if len(active) != 0 {
		t.Fatalf("active comments = %d, want 0", len(active))
	}
	history, err := service.ListCommentsByAnchor(ctx, "doc:guide/setup", anchor, true)
	if err != nil {
		t.Fatalf("list history: %v", err)
	}
	if len(history) != 3 {
		t.Fatalf("history comments = %d, want 3", len(history))
	}
	for _, comment := range history {
		if comment.ResolvedAt == nil {
			t.Errorf("comment %s was not resolved", comment.ID)
		}
	}
	if _, err := service.CreateComment(ctx, principal, CreateCommentInput{
		DocumentID: "doc:guide/setup", VersionHash: "hash", Content: "Too late", ParentID: &grandchild.ID,
	}); err == nil {
		t.Fatal("reply to resolved thread should fail")
	}
}
