package repo

import (
	"context"
	"database/sql"
	"path/filepath"
	"testing"

	"github.com/pressly/goose/v3"
	_ "modernc.org/sqlite"
)

func openRaw(t *testing.T, name string) (*sql.DB, string) {
	t.Helper()
	dsn := "file:" + filepath.Join(t.TempDir(), name) + "?_pragma=foreign_keys(ON)"
	db, err := sql.Open("sqlite", dsn)
	if err != nil {
		t.Fatal(err)
	}
	return db, dsn
}

func TestMigrateFreshDatabase(t *testing.T) {
	ctx := context.Background()
	db, _ := openRaw(t, "fresh.db")
	defer db.Close()

	if err := migrate(ctx, db); err != nil {
		t.Fatalf("migrate: %v", err)
	}

	for _, col := range []string{"needs_reauth", "auth_error"} {
		ok, err := columnExists(ctx, db, "accounts", col)
		if err != nil || !ok {
			t.Fatalf("column %q missing on fresh db: ok=%v err=%v", col, ok, err)
		}
	}
}

// A database created by the old auto-migration has the pre-reauth schema and no
// goose history; it must be baselined and then upgraded without losing rows.
func TestMigrateBaselinesLegacyDatabase(t *testing.T) {
	ctx := context.Background()
	db, _ := openRaw(t, "legacy.db")
	defer db.Close()

	_, err := db.ExecContext(ctx, `CREATE TABLE accounts (
		id text NOT NULL, kind text NOT NULL, display_name text NOT NULL,
		settings json NULL, created_at datetime NOT NULL, updated_at datetime NOT NULL,
		PRIMARY KEY (id));
		INSERT INTO accounts (id, kind, display_name, created_at, updated_at)
		VALUES ('a@b.com','google','A','2026-01-01','2026-01-01');
		CREATE TABLE calendars (
		id text NOT NULL, remote_id text NOT NULL, name text NOT NULL,
		name_override text NULL, description text NULL, color text NULL,
		time_zone text NULL, read_only bool NOT NULL DEFAULT (false),
		hidden bool NOT NULL DEFAULT (false), sync_token text NULL,
		created_at datetime NOT NULL, updated_at datetime NOT NULL,
		account_calendars text NOT NULL, PRIMARY KEY (id));
		CREATE TABLE events (
		id text NOT NULL, uid text NOT NULL, summary text NOT NULL,
		status text NOT NULL DEFAULT ('confirmed'), start datetime NOT NULL,
		end datetime NOT NULL, all_day bool NOT NULL DEFAULT (false),
		recurrence json NULL, recurring_id text NULL,
		created datetime NOT NULL, updated datetime NOT NULL,
		calendar_events text NOT NULL, PRIMARY KEY (id));
		CREATE INDEX event_recurring_id ON events (recurring_id);
		CREATE TABLE reminder_states (
		id integer NOT NULL PRIMARY KEY AUTOINCREMENT, calendar_id text NOT NULL,
		uid text NOT NULL, occurrence_start datetime NOT NULL, minutes integer NOT NULL,
		fired_at datetime NOT NULL, snoozed_until datetime NULL);
		CREATE INDEX reminderstate_snoozed_until ON reminder_states (snoozed_until);`)
	if err != nil {
		t.Fatal(err)
	}

	if err := migrate(ctx, db); err != nil {
		t.Fatalf("migrate legacy: %v", err)
	}

	ok, err := columnExists(ctx, db, "accounts", "needs_reauth")
	if err != nil || !ok {
		t.Fatalf("needs_reauth missing after upgrade: ok=%v err=%v", ok, err)
	}
	var n int
	if err := db.QueryRowContext(ctx, "SELECT count(*) FROM accounts WHERE id = 'a@b.com';").Scan(&n); err != nil || n != 1 {
		t.Fatalf("existing row not preserved: n=%d err=%v", n, err)
	}
}

func TestMigrateIsIdempotent(t *testing.T) {
	ctx := context.Background()
	db, _ := openRaw(t, "idem.db")
	defer db.Close()

	for i := 0; i < 3; i++ {
		if err := migrate(ctx, db); err != nil {
			t.Fatalf("migrate run %d: %v", i, err)
		}
	}
}

func TestMeetingURLMigrationInvalidatesSyncTokens(t *testing.T) {
	ctx := context.Background()
	db, _ := openRaw(t, "meeting-urls.db")
	defer db.Close()

	configureGoose()
	const previousVersion = 20260629125249
	if err := goose.UpToContext(ctx, db, migrationsDir, previousVersion); err != nil {
		t.Fatalf("migrate to previous version: %v", err)
	}

	_, err := db.ExecContext(ctx, `INSERT INTO accounts
		(id, kind, display_name, created_at, updated_at)
		VALUES ('account-1', 'google', 'Test', '2026-01-01', '2026-01-01');
		INSERT INTO calendars
		(id, remote_id, name, sync_token, created_at, updated_at, account_calendars)
		VALUES ('calendar-1', 'remote-1', 'Test', 'cursor-1', '2026-01-01', '2026-01-01', 'account-1');`)
	if err != nil {
		t.Fatal(err)
	}

	if err := migrate(ctx, db); err != nil {
		t.Fatalf("migrate: %v", err)
	}

	var token sql.NullString
	if err := db.QueryRowContext(ctx, "SELECT sync_token FROM calendars WHERE id = 'calendar-1';").Scan(&token); err != nil {
		t.Fatal(err)
	}
	if token.Valid {
		t.Fatalf("sync token was not invalidated: %q", token.String)
	}
}
