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

const repoMock = {
  list: vi.fn(),
  create: vi.fn(),
  getById: vi.fn(),
  update: vi.fn(),
  listExpired: vi.fn(),
  listAll: vi.fn(),
};

const notifyMock = {
  notifyReviewAssigned: vi.fn(),
};

vi.mock('./repository.js', () => repoMock);
vi.mock('./notifications.js', () => notifyMock);

async function buildApp(payload?: { sub: string; productId: string; role?: string }) {
  const { reviewRoutes } = await import('./routes.js');
  const app = Fastify({ logger: false });
  if (payload) {
    app.addHook('onRequest', async req => {
      req.jwtPayload = payload;
    });
  }
  await app.register(reviewRoutes, { prefix: '/api' });
  return app;
}

describe('reviewRoutes', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

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

  it('POST /reviews creates a pending review item', async () => {
    repoMock.create.mockResolvedValue({ id: 'rev_1', status: 'pending' });
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });

    const res = await app.inject({
      method: 'POST',
      url: '/api/reviews',
      payload: {
        title: 'Approve agent escalation',
        description: 'The agent wants to escalate this incident.',
        category: 'agent_action',
        orgId: 'org_1',
        source: 'mcp.a2a',
        actionType: 'escalate_incident',
      },
    });

    expect(res.statusCode).toBe(200);
    expect(repoMock.create).toHaveBeenCalledWith(
      expect.objectContaining({
        requestedBy: 'admin_1',
        status: 'pending',
      })
    );
  });

  it('POST /reviews/:id/decision records an approval decision', async () => {
    repoMock.update.mockResolvedValue({ id: 'rev_1', status: 'approved' });
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });

    const res = await app.inject({
      method: 'POST',
      url: '/api/reviews/rev_1/decision',
      payload: { decision: 'approved', reason: 'Looks safe' },
    });

    expect(res.statusCode).toBe(200);
    expect(repoMock.update).toHaveBeenCalledWith(
      'rev_1',
      'lysnrai',
      expect.objectContaining({
        status: 'approved',
        resolution: expect.objectContaining({
          decision: 'approved',
          actedBy: 'admin_1',
        }),
      })
    );
  });

  // ── Batch Decisions ────────────────────────────────────

  it('POST /reviews/batch-decision processes batch approvals', async () => {
    repoMock.update.mockResolvedValue({ status: 'approved' });
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });

    const res = await app.inject({
      method: 'POST',
      url: '/api/reviews/batch-decision',
      payload: { ids: ['rev_1', 'rev_2'], decision: 'approved', reason: 'Batch OK' },
    });

    expect(res.statusCode).toBe(200);
    const body = res.json();
    expect(body.succeeded).toBe(2);
    expect(body.total).toBe(2);
  });

  it('POST /reviews/batch-decision rejects empty ids', async () => {
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const res = await app.inject({
      method: 'POST',
      url: '/api/reviews/batch-decision',
      payload: { ids: [], decision: 'approved' },
    });
    expect(res.statusCode).toBe(400);
  });

  it('POST /reviews/batch-decision rejects > 50 items', async () => {
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const ids = Array.from({ length: 51 }, (_, i) => `rev_${i}`);
    const res = await app.inject({
      method: 'POST',
      url: '/api/reviews/batch-decision',
      payload: { ids, decision: 'approved' },
    });
    expect(res.statusCode).toBe(400);
  });

  // ── Delegation ─────────────────────────────────────────

  it('POST /reviews/:id/delegate reassigns and notifies', async () => {
    repoMock.getById.mockResolvedValue({ id: 'rev_1', assignedTo: 'user_1', metadata: {} });
    repoMock.update.mockResolvedValue({ id: 'rev_1', assignedTo: 'user_2', status: 'assigned' });
    notifyMock.notifyReviewAssigned.mockResolvedValue(undefined);
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });

    const res = await app.inject({
      method: 'POST',
      url: '/api/reviews/rev_1/delegate',
      payload: { delegateTo: 'user_2', reason: 'SME needed' },
    });

    expect(res.statusCode).toBe(200);
    expect(repoMock.update).toHaveBeenCalledWith(
      'rev_1',
      'lysnrai',
      expect.objectContaining({
        assignedTo: 'user_2',
        status: 'assigned',
      })
    );
    expect(notifyMock.notifyReviewAssigned).toHaveBeenCalled();
  });

  it('POST /reviews/:id/delegate requires delegateTo', async () => {
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const res = await app.inject({
      method: 'POST',
      url: '/api/reviews/rev_1/delegate',
      payload: {},
    });
    expect(res.statusCode).toBe(400);
  });

  // ── Auto-Expiry ────────────────────────────────────────

  it('POST /reviews/expire expires overdue reviews', async () => {
    repoMock.listExpired.mockResolvedValue([
      { id: 'rev_1', dueAt: '2020-01-01' },
      { id: 'rev_2', dueAt: '2020-01-02' },
    ]);
    repoMock.update.mockResolvedValue({ status: 'expired' });
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });

    const res = await app.inject({ method: 'POST', url: '/api/reviews/expire' });
    expect(res.statusCode).toBe(200);
    expect(res.json().expired).toBe(2);
  });

  // ── Stats ─────────────────────────────────────────────

  it('GET /reviews/stats returns review queue metrics', async () => {
    repoMock.listAll.mockResolvedValue([
      {
        id: 'rev_1',
        status: 'pending',
        priority: 'high',
        category: 'agent_action',
        createdAt: '2026-03-15T00:00:00Z',
        dueAt: '2020-01-01',
      },
      {
        id: 'rev_2',
        status: 'approved',
        priority: 'normal',
        category: 'agent_action',
        createdAt: '2026-03-14T00:00:00Z',
        resolution: { actedAt: '2026-03-15T00:00:00Z' },
      },
      {
        id: 'rev_3',
        status: 'rejected',
        priority: 'low',
        category: 'model_change',
        createdAt: '2026-03-13T00:00:00Z',
        resolution: { actedAt: '2026-03-14T00:00:00Z' },
      },
    ]);
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });

    const res = await app.inject({ method: 'GET', url: '/api/reviews/stats' });
    expect(res.statusCode).toBe(200);
    const body = res.json();
    expect(body.total).toBe(3);
    expect(body.byStatus.pending).toBe(1);
    expect(body.byStatus.approved).toBe(1);
    expect(body.pendingCount).toBe(1);
    expect(body.overdueCount).toBe(1);
  });
});
