/**
 * FED-M1-08 — Integration test: PGlite → federated Postgres+pgvector migration.
 *
 * Prereq: docker compose -f docker-compose.federated.yml --profile federated up -d
 * Run:    FEDERATED_INTEGRATION=1 pnpm --filter @mosaicstack/storage test src/migrate-tier.integration.test.ts
 *
 * Skipped when FEDERATED_INTEGRATION !== '1'.
 *
 * Strategy: users.id (TEXT PK) uses the recognisable prefix `fed-m1-08-` for
 * easy cleanup. UUID-PKed tables (teams, conversations, messages, team_members)
 * use deterministic valid UUIDs in the `f0000xxx-…` namespace. Cleanup is
 * explicit DELETE by id — no full-table truncation.
 */

import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';

import {
  users,
  teams,
  teamMembers,
  conversations,
  messages,
  createPgliteDb,
  runPgliteMigrations,
} from '@mosaicstack/db';

import postgres from 'postgres';
import { afterAll, describe, expect, it } from 'vitest';

import { DrizzleMigrationSource, PostgresMigrationTarget, runMigrateTier } from './migrate-tier.js';

/* ------------------------------------------------------------------ */
/*  Constants                                                           */
/* ------------------------------------------------------------------ */

const run = process.env['FEDERATED_INTEGRATION'] === '1';

const FEDERATED_PG_URL = 'postgresql://mosaic:mosaic@localhost:5433/mosaic';

/**
 * Deterministic IDs for the test's seed data.
 *
 * users.id is TEXT (any string) — we use a recognisable prefix for easy cleanup.
 * All other tables use UUID primary keys — must be valid UUID v4 format.
 * The 4th segment starts with '4' (version 4) and 5th starts with '8' (variant).
 */
const IDS = {
  // text PK — can be any string
  user1: 'fed-m1-08-user-1',
  user2: 'fed-m1-08-user-2',
  // UUID PKs — must be valid UUID format
  team1: 'f0000001-0000-4000-8000-000000000001',
  teamMember1: 'f0000002-0000-4000-8000-000000000001',
  teamMember2: 'f0000002-0000-4000-8000-000000000002',
  conv1: 'f0000003-0000-4000-8000-000000000001',
  conv2: 'f0000003-0000-4000-8000-000000000002',
  msg1: 'f0000004-0000-4000-8000-000000000001',
  msg2: 'f0000004-0000-4000-8000-000000000002',
  msg3: 'f0000004-0000-4000-8000-000000000003',
  msg4: 'f0000004-0000-4000-8000-000000000004',
  msg5: 'f0000004-0000-4000-8000-000000000005',
} as const;

/* ------------------------------------------------------------------ */
/*  Shared handles for afterAll cleanup                                 */
/* ------------------------------------------------------------------ */

let targetSql: ReturnType<typeof postgres> | undefined;
let pgliteDataDir: string | undefined;

afterAll(async () => {
  if (targetSql) {
    await cleanTarget(targetSql).catch(() => {});
    await targetSql.end({ timeout: 5 }).catch(() => {});
  }
  if (pgliteDataDir) {
    await fs.rm(pgliteDataDir, { recursive: true, force: true }).catch(() => {});
  }
});

/* ------------------------------------------------------------------ */
/*  Helpers                                                             */
/* ------------------------------------------------------------------ */

/** Delete all test-owned rows from target in safe FK order. */
async function cleanTarget(sql: ReturnType<typeof postgres>): Promise<void> {
  // Reverse FK order: messages → conversations → team_members → teams → users
  await sql.unsafe(`DELETE FROM messages WHERE id = ANY($1)`, [
    [IDS.msg1, IDS.msg2, IDS.msg3, IDS.msg4, IDS.msg5],
  ] as never[]);
  await sql.unsafe(`DELETE FROM conversations WHERE id = ANY($1)`, [
    [IDS.conv1, IDS.conv2],
  ] as never[]);
  await sql.unsafe(`DELETE FROM team_members WHERE id = ANY($1)`, [
    [IDS.teamMember1, IDS.teamMember2],
  ] as never[]);
  await sql.unsafe(`DELETE FROM teams WHERE id = $1`, [IDS.team1] as never[]);
  await sql.unsafe(`DELETE FROM users WHERE id = ANY($1)`, [[IDS.user1, IDS.user2]] as never[]);
}

/* ------------------------------------------------------------------ */
/*  Test suite                                                          */
/* ------------------------------------------------------------------ */

describe.skipIf(!run)('migrate-tier — PGlite → federated PG', () => {
  it('seeds PGlite, runs migrate-tier, asserts row counts and sample rows on target', async () => {
    /* ---- 1. Create a temp PGlite db ---------------------------------- */

    pgliteDataDir = await fs.mkdtemp(path.join(os.tmpdir(), 'fed-m1-08-'));
    const handle = createPgliteDb(pgliteDataDir);
    await runPgliteMigrations(handle);

    /* ---- 2. Seed representative data --------------------------------- */

    const now = new Date();
    const db = handle.db;

    // users (2 rows)
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    await (db as any).insert(users).values([
      {
        id: IDS.user1,
        name: 'Fed Test User One',
        email: 'fed-m1-08-user1@test.invalid',
        emailVerified: false,
        role: 'member',
        createdAt: now,
        updatedAt: now,
      },
      {
        id: IDS.user2,
        name: 'Fed Test User Two',
        email: 'fed-m1-08-user2@test.invalid',
        emailVerified: false,
        role: 'member',
        createdAt: now,
        updatedAt: now,
      },
    ]);

    // teams (1 row)
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    await (db as any).insert(teams).values([
      {
        id: IDS.team1,
        name: 'Fed M1-08 Team',
        slug: 'fed-m1-08-team',
        ownerId: IDS.user1,
        managerId: IDS.user1,
        createdAt: now,
        updatedAt: now,
      },
    ]);

    // team_members (2 rows linking both users to the team)
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    await (db as any).insert(teamMembers).values([
      {
        id: IDS.teamMember1,
        teamId: IDS.team1,
        userId: IDS.user1,
        role: 'manager',
        joinedAt: now,
      },
      {
        id: IDS.teamMember2,
        teamId: IDS.team1,
        userId: IDS.user2,
        role: 'member',
        joinedAt: now,
      },
    ]);

    // conversations (2 rows)
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    await (db as any).insert(conversations).values([
      {
        id: IDS.conv1,
        title: 'Fed M1-08 Conversation Alpha',
        userId: IDS.user1,
        archived: false,
        createdAt: now,
        updatedAt: now,
      },
      {
        id: IDS.conv2,
        title: 'Fed M1-08 Conversation Beta',
        userId: IDS.user2,
        archived: false,
        createdAt: now,
        updatedAt: now,
      },
    ]);

    // messages (5 rows across both conversations)
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    await (db as any).insert(messages).values([
      {
        id: IDS.msg1,
        conversationId: IDS.conv1,
        role: 'user',
        content: 'Hello from conv1 msg1',
        createdAt: now,
      },
      {
        id: IDS.msg2,
        conversationId: IDS.conv1,
        role: 'assistant',
        content: 'Reply in conv1 msg2',
        createdAt: now,
      },
      {
        id: IDS.msg3,
        conversationId: IDS.conv1,
        role: 'user',
        content: 'Follow-up in conv1 msg3',
        createdAt: now,
      },
      {
        id: IDS.msg4,
        conversationId: IDS.conv2,
        role: 'user',
        content: 'Hello from conv2 msg4',
        createdAt: now,
      },
      {
        id: IDS.msg5,
        conversationId: IDS.conv2,
        role: 'assistant',
        content: 'Reply in conv2 msg5',
        createdAt: now,
      },
    ]);

    /* ---- 3. Pre-clean the target so the test is repeatable ----------- */

    targetSql = postgres(FEDERATED_PG_URL, {
      max: 3,
      connect_timeout: 10,
      idle_timeout: 30,
    });

    await cleanTarget(targetSql);

    /* ---- 4. Build source / target adapters and run migration --------- */

    const source = new DrizzleMigrationSource(db, /* sourceHasVector= */ false);
    const target = new PostgresMigrationTarget(FEDERATED_PG_URL);

    try {
      await runMigrateTier(
        source,
        target,
        {
          targetUrl: FEDERATED_PG_URL,
          dryRun: false,
          allowNonEmpty: true,
          batchSize: 500,
          onProgress: (_msg) => {
            // Uncomment for debugging: console.log(_msg);
          },
        },
        /* sourceHasVector= */ false,
      );
    } finally {
      await target.close();
    }

    /* ---- 5. Assert: row counts in target match seed ------------------ */

    const countUsers = await targetSql.unsafe<Array<{ n: string }>>(
      `SELECT COUNT(*)::text AS n FROM users WHERE id = ANY($1)`,
      [[IDS.user1, IDS.user2]] as never[],
    );
    expect(Number(countUsers[0]?.n)).toBe(2);

    const countTeams = await targetSql.unsafe<Array<{ n: string }>>(
      `SELECT COUNT(*)::text AS n FROM teams WHERE id = $1`,
      [IDS.team1] as never[],
    );
    expect(Number(countTeams[0]?.n)).toBe(1);

    const countTeamMembers = await targetSql.unsafe<Array<{ n: string }>>(
      `SELECT COUNT(*)::text AS n FROM team_members WHERE id = ANY($1)`,
      [[IDS.teamMember1, IDS.teamMember2]] as never[],
    );
    expect(Number(countTeamMembers[0]?.n)).toBe(2);

    const countConvs = await targetSql.unsafe<Array<{ n: string }>>(
      `SELECT COUNT(*)::text AS n FROM conversations WHERE id = ANY($1)`,
      [[IDS.conv1, IDS.conv2]] as never[],
    );
    expect(Number(countConvs[0]?.n)).toBe(2);

    const countMsgs = await targetSql.unsafe<Array<{ n: string }>>(
      `SELECT COUNT(*)::text AS n FROM messages WHERE id = ANY($1)`,
      [[IDS.msg1, IDS.msg2, IDS.msg3, IDS.msg4, IDS.msg5]] as never[],
    );
    expect(Number(countMsgs[0]?.n)).toBe(5);

    /* ---- 6. Assert: sample row field values --------------------------- */

    // User 1: check email and name
    const userRows = await targetSql.unsafe<Array<{ id: string; email: string; name: string }>>(
      `SELECT id, email, name FROM users WHERE id = $1`,
      [IDS.user1] as never[],
    );
    expect(userRows[0]?.email).toBe('fed-m1-08-user1@test.invalid');
    expect(userRows[0]?.name).toBe('Fed Test User One');

    // Conversation 1: check title and user_id
    const convRows = await targetSql.unsafe<Array<{ id: string; title: string; user_id: string }>>(
      `SELECT id, title, user_id FROM conversations WHERE id = $1`,
      [IDS.conv1] as never[],
    );
    expect(convRows[0]?.title).toBe('Fed M1-08 Conversation Alpha');
    expect(convRows[0]?.user_id).toBe(IDS.user1);

    /* ---- 7. Cleanup: delete test rows from target -------------------- */

    await cleanTarget(targetSql);

    // Close PGlite
    await handle.close();
  }, 60_000);
});
