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

const repoMock = {
  upsertTimelineItems: vi.fn(),
  listTimelineItems: vi.fn(),
};

vi.mock('./repository.js', () => repoMock);
vi.mock('../../lib/api-key-auth.js', () => ({
  requireJwtOrApiKey: vi.fn(async req => {
    const roleHeader = req.headers['x-test-role'];
    const userHeader = req.headers['x-test-user'];
    const productHeader = req.headers['x-test-product'];
    req.jwtPayload =
      typeof userHeader === 'string'
        ? {
            sub: userHeader,
            role: typeof roleHeader === 'string' ? roleHeader : undefined,
            productId: typeof productHeader === 'string' ? productHeader : 'lysnrai',
          }
        : undefined;

    return {
      actorId: typeof userHeader === 'string' ? userHeader : 'svc_timeline',
      productId: typeof productHeader === 'string' ? productHeader : 'lysnrai',
      source: typeof userHeader === 'string' ? 'jwt' : 'api_key',
    };
  }),
}));

async function buildApp() {
  const { timelineRoutes } = await import('./routes.js');
  const app = Fastify({ logger: false });
  await app.register(timelineRoutes, { prefix: '/api' });
  return app;
}

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

  it('POST /timeline/ingest persists canonical events as timeline items', async () => {
    repoMock.upsertTimelineItems.mockImplementation(async docs => docs);
    const app = await buildApp();

    const res = await app.inject({
      method: 'POST',
      url: '/api/timeline/ingest',
      headers: {
        'x-test-user': 'user_1',
        'x-test-product': 'lysnrai',
      },
      payload: {
        events: [
          {
            eventId: 'evt_1',
            eventName: 'capture.transcript.created',
            eventVersion: 1,
            occurredAt: '2026-04-04T10:00:00.000Z',
            productId: 'lysnrai',
            sourceSurface: 'voice',
            actor: { actorType: 'user', actorId: 'user_1' },
            trace: { correlationId: 'corr_1', causationId: null, parentEventId: null },
            payload: {
              artifactId: 'art_1',
              durationMs: 1200,
              language: 'en',
              transcriptSource: 'microphone',
            },
          },
        ],
      },
    });

    expect(res.statusCode).toBe(200);
    const body = JSON.parse(res.body);
    expect(body.ingestedCount).toBe(1);
    expect(body.items[0].title).toBe('Transcript captured');
    expect(repoMock.upsertTimelineItems).toHaveBeenCalledOnce();
    expect(repoMock.upsertTimelineItems.mock.calls[0][0][0]).toMatchObject({
      productId: 'lysnrai',
      userId: 'user_1',
      eventId: 'evt_1',
      visibility: 'private',
    });
  });

  it('POST /timeline/ingest rejects cross-product events', async () => {
    const app = await buildApp();

    const res = await app.inject({
      method: 'POST',
      url: '/api/timeline/ingest',
      headers: {
        'x-test-user': 'user_1',
        'x-test-product': 'lysnrai',
      },
      payload: {
        events: [
          {
            eventId: 'evt_2',
            eventName: 'artifact.created',
            eventVersion: 1,
            occurredAt: '2026-04-04T10:00:00.000Z',
            productId: 'notelett',
            sourceSurface: 'notes',
            actor: { actorType: 'user', actorId: 'user_1' },
            trace: { correlationId: null, causationId: null, parentEventId: null },
            payload: {
              artifactType: 'note',
              title: 'Quick note',
              status: 'created',
            },
          },
        ],
      },
    });

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

  it('GET /timeline scopes non-admin JWT callers to their own timeline', async () => {
    repoMock.listTimelineItems.mockResolvedValue([
      {
        id: 'timeline_evt_1',
        eventId: 'evt_1',
        eventName: 'artifact.created',
        productId: 'lysnrai',
        userId: 'user_1',
        orgId: null,
        artifactId: 'art_1',
        correlationId: 'corr_1',
        actorType: 'user',
        visibility: 'private',
        occurredAt: '2026-04-04T10:00:00.000Z',
        item: {
          itemId: 'timeline_evt_1',
          occurredAt: '2026-04-04T10:00:00.000Z',
          eventName: 'artifact.created',
          productId: 'lysnrai',
          title: 'Note created',
          summary: 'note status: created',
          artifactRefs: ['art_1'],
          relatedEventIds: [],
          actorType: 'user',
          visibility: 'private',
          correlationId: 'corr_1',
        },
        createdAt: '2026-04-04T10:00:00.000Z',
        updatedAt: '2026-04-04T10:00:00.000Z',
      },
    ]);
    const app = await buildApp();

    const res = await app.inject({
      method: 'GET',
      url: '/api/timeline?limit=10',
      headers: {
        'x-test-user': 'user_1',
        'x-test-product': 'lysnrai',
      },
    });

    expect(res.statusCode).toBe(200);
    expect(repoMock.listTimelineItems).toHaveBeenCalledWith(
      'lysnrai',
      expect.objectContaining({ userId: 'user_1', limit: 10 })
    );
    expect(JSON.parse(res.body).count).toBe(1);
  });

  it('GET /timeline forbids non-admin JWT callers from querying another user', async () => {
    const app = await buildApp();

    const res = await app.inject({
      method: 'GET',
      url: '/api/timeline?userId=user_2',
      headers: {
        'x-test-user': 'user_1',
        'x-test-product': 'lysnrai',
      },
    });

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