/**
 * Route tests for referrals migration admin endpoints.
 */

import Fastify from 'fastify';

import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest';

const migrationRepoMock = {
  getMigrationStatus: vi.fn(),
  backfillOldToNew: vi.fn(),
  verifyConsistency: vi.fn(),
};

const authMock = {
  requireRole: vi.fn(),
};

const requestContextMock = {
  getRequestProductId: () => 'lysnrai',
};

vi.mock('./migration-repository.js', () => migrationRepoMock);
vi.mock('../../lib/auth.js', () => authMock);
vi.mock('../../lib/request-context.js', () => requestContextMock);
vi.mock('../../lib/errors.js', () => ({
  BadRequestError: class BadRequestError extends Error {
    statusCode = 400;
    constructor(message: string) {
      super(message);
      this.name = 'BadRequestError';
    }
  },
}));

describe('referralMigrationAdminRoutes', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    // Mock successful auth
    authMock.requireRole.mockResolvedValue(undefined);
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  describe('GET /admin/referrals/migration/status', () => {
    it('returns migration status', async () => {
      migrationRepoMock.getMigrationStatus.mockResolvedValue({
        mode: 'dual-write',
        oldContainerCount: 10,
        newContainerCount: 8,
        pendingBackfillCount: 2,
      });

      const { referralMigrationAdminRoutes } = await import('./migration-admin-routes.js');
      const app = Fastify({ logger: false });
      await app.register(referralMigrationAdminRoutes, { prefix: '/api/admin' });

      const res = await app.inject({
        method: 'GET',
        url: '/api/admin/referrals/migration/status',
      });

      expect(res.statusCode).toBe(200);
      const data = JSON.parse(res.body);
      expect(data.mode).toBe('dual-write');
      expect(data.oldContainerCount).toBe(10);
      expect(data.newContainerCount).toBe(8);
      expect(data.pendingBackfillCount).toBe(2);
    });

    it('requires admin role', async () => {
      authMock.requireRole.mockRejectedValue(new Error('Forbidden'));

      const { referralMigrationAdminRoutes } = await import('./migration-admin-routes.js');
      const app = Fastify({ logger: false });
      await app.register(referralMigrationAdminRoutes, { prefix: '/api/admin' });

      const res = await app.inject({
        method: 'GET',
        url: '/api/admin/referrals/migration/status',
      });

      expect(res.statusCode).toBe(500);
    });
  });

  describe('POST /admin/referrals/migration/backfill', () => {
    it('triggers backfill with default batch size', async () => {
      migrationRepoMock.backfillOldToNew.mockResolvedValue({
        migrated: 5,
        skipped: 2,
        errors: [],
      });

      const { referralMigrationAdminRoutes } = await import('./migration-admin-routes.js');
      const app = Fastify({ logger: false });
      await app.register(referralMigrationAdminRoutes, { prefix: '/api/admin' });

      const res = await app.inject({
        method: 'POST',
        url: '/api/admin/referrals/migration/backfill',
      });

      expect(res.statusCode).toBe(200);
      const data = JSON.parse(res.body);
      expect(data.success).toBe(true);
      expect(data.migrated).toBe(5);
      expect(data.skipped).toBe(2);
    });

    it('accepts custom batch size', async () => {
      migrationRepoMock.backfillOldToNew.mockResolvedValue({
        migrated: 10,
        skipped: 0,
        errors: [],
      });

      const { referralMigrationAdminRoutes } = await import('./migration-admin-routes.js');
      const app = Fastify({ logger: false });
      await app.register(referralMigrationAdminRoutes, { prefix: '/api/admin' });

      const res = await app.inject({
        method: 'POST',
        url: '/api/admin/referrals/migration/backfill?batchSize=50',
      });

      expect(res.statusCode).toBe(200);
      expect(migrationRepoMock.backfillOldToNew).toHaveBeenCalledWith('lysnrai', 50);
    });

    it('returns errors if backfill has issues', async () => {
      migrationRepoMock.backfillOldToNew.mockResolvedValue({
        migrated: 3,
        skipped: 1,
        errors: ['Doc ref_1: missing referrerId'],
      });

      const { referralMigrationAdminRoutes } = await import('./migration-admin-routes.js');
      const app = Fastify({ logger: false });
      await app.register(referralMigrationAdminRoutes, { prefix: '/api/admin' });

      const res = await app.inject({
        method: 'POST',
        url: '/api/admin/referrals/migration/backfill',
      });

      expect(res.statusCode).toBe(200);
      const data = JSON.parse(res.body);
      expect(data.errors).toHaveLength(1);
      expect(data.message).toContain('1 errors');
    });
  });

  describe('GET /admin/referrals/migration/verify', () => {
    it('returns consistency check results', async () => {
      migrationRepoMock.verifyConsistency.mockResolvedValue([
        { id: 'ref_1', issue: 'pending backfill (exists in old, missing in new)' },
        { id: 'ref_2', issue: 'status mismatch: pending vs signed_up' },
      ]);

      const { referralMigrationAdminRoutes } = await import('./migration-admin-routes.js');
      const app = Fastify({ logger: false });
      await app.register(referralMigrationAdminRoutes, { prefix: '/api/admin' });

      const res = await app.inject({
        method: 'GET',
        url: '/api/admin/referrals/migration/verify',
      });

      expect(res.statusCode).toBe(200);
      const data = JSON.parse(res.body);
      expect(data.consistent).toBe(false);
      expect(data.count).toBe(1); // pending backfill filtered out by default
      expect(data.totalPending).toBe(1);
    });

    it('includes pending backfill when requested', async () => {
      migrationRepoMock.verifyConsistency.mockResolvedValue([
        { id: 'ref_1', issue: 'pending backfill (exists in old, missing in new)' },
      ]);

      const { referralMigrationAdminRoutes } = await import('./migration-admin-routes.js');
      const app = Fastify({ logger: false });
      await app.register(referralMigrationAdminRoutes, { prefix: '/api/admin' });

      const res = await app.inject({
        method: 'GET',
        url: '/api/admin/referrals/migration/verify?includePending=true',
      });

      expect(res.statusCode).toBe(200);
      const data = JSON.parse(res.body);
      expect(data.count).toBe(1);
      expect(data.inconsistencies).toHaveLength(1);
    });

    it('returns consistent true when no issues', async () => {
      migrationRepoMock.verifyConsistency.mockResolvedValue([]);

      const { referralMigrationAdminRoutes } = await import('./migration-admin-routes.js');
      const app = Fastify({ logger: false });
      await app.register(referralMigrationAdminRoutes, { prefix: '/api/admin' });

      const res = await app.inject({
        method: 'GET',
        url: '/api/admin/referrals/migration/verify',
      });

      expect(res.statusCode).toBe(200);
      const data = JSON.parse(res.body);
      expect(data.consistent).toBe(true);
      expect(data.count).toBe(0);
    });
  });

  describe('POST /admin/referrals/migration/mode', () => {
    it('switches to new-only mode', async () => {
      const previousMode = process.env.REFERRAL_MIGRATION_MODE;
      process.env.REFERRAL_MIGRATION_MODE = 'dual-write';

      const { referralMigrationAdminRoutes } = await import('./migration-admin-routes.js');
      const app = Fastify({ logger: false });
      await app.register(referralMigrationAdminRoutes, { prefix: '/api/admin' });

      const res = await app.inject({
        method: 'POST',
        url: '/api/admin/referrals/migration/mode',
        payload: { mode: 'new-only' },
      });

      expect(res.statusCode).toBe(200);
      const data = JSON.parse(res.body);
      expect(data.success).toBe(true);
      expect(data.previousMode).toBe('dual-write');
      expect(data.currentMode).toBe('new-only');
      expect(data.warning).toContain('New-only mode');

      // Restore
      process.env.REFERRAL_MIGRATION_MODE = previousMode;
    });

    it('rejects invalid mode', async () => {
      const { referralMigrationAdminRoutes } = await import('./migration-admin-routes.js');
      const app = Fastify({ logger: false });
      await app.register(referralMigrationAdminRoutes, { prefix: '/api/admin' });

      const res = await app.inject({
        method: 'POST',
        url: '/api/admin/referrals/migration/mode',
        payload: { mode: 'invalid-mode' },
      });

      expect(res.statusCode).toBe(400);
    });

    it('requires confirmation for old-only mode', async () => {
      process.env.REFERRAL_MIGRATION_MODE = 'dual-write';

      const { referralMigrationAdminRoutes } = await import('./migration-admin-routes.js');
      const app = Fastify({ logger: false });
      await app.register(referralMigrationAdminRoutes, { prefix: '/api/admin' });

      const res = await app.inject({
        method: 'POST',
        url: '/api/admin/referrals/migration/mode',
        payload: { mode: 'old-only' },
      });

      expect(res.statusCode).toBe(400);
      const data = JSON.parse(res.body);
      expect(data.message).toContain('confirm=true');
    });

    it('allows old-only with confirmation', async () => {
      process.env.REFERRAL_MIGRATION_MODE = 'dual-write';

      const { referralMigrationAdminRoutes } = await import('./migration-admin-routes.js');
      const app = Fastify({ logger: false });
      await app.register(referralMigrationAdminRoutes, { prefix: '/api/admin' });

      const res = await app.inject({
        method: 'POST',
        url: '/api/admin/referrals/migration/mode',
        payload: { mode: 'old-only', confirm: true },
      });

      expect(res.statusCode).toBe(200);
      const data = JSON.parse(res.body);
      expect(data.success).toBe(true);
      expect(data.currentMode).toBe('old-only');
      expect(data.warning).toContain('Reverted');
    });
  });
});
