import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import express from 'express';
import request from 'supertest';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { randomUUID } from 'node:crypto';
import { Repository } from '../db/repository.js';
import { createCrossCalendarApi } from './cross-calendar-api.js';

type TestUser = { id: string; role: 'user' | 'admin'; orgIds: string[] };

function makeApp(repo: Repository, user: TestUser | null) {
  const app = express();
  app.use((req, _res, next) => {
    if (user) (req as any).user = user;
    next();
  });
  app.use('/api/calendar', createCrossCalendarApi({ repo, authActive: !!user }));
  return app;
}

describe('cross-calendar API GET /api/calendar/cross', () => {
  let repo: Repository;
  let dir: string;

  const owner: TestUser = { id: 'u1', role: 'user', orgIds: [] };
  const stranger: TestUser = { id: 'u2', role: 'user', orgIds: [] };

  beforeEach(() => {
    dir = mkdtempSync(join(tmpdir(), 'cross-cal-api-'));
    repo = new Repository(join(dir, `${randomUUID()}.db`));
  });
  afterEach(() => {
    repo.close();
    rmSync(dir, { recursive: true, force: true });
  });

  it('returns aggregated shape for the viewer (days/events/spaces)', async () => {
    const a = await repo.createSpace({ kind: 'case', title: 'Alpha', ownerId: 'u1', visibility: 'private' });
    await repo.createCalendarEvent({ spaceId: a.id, date: '2026-06-10', title: 'evt' });

    const res = await request(makeApp(repo, owner)).get('/api/calendar/cross?month=2026-06&tz_offset=540');
    expect(res.status).toBe(200);
    expect(res.body).toHaveProperty('days');
    expect(res.body).toHaveProperty('events');
    expect(res.body).toHaveProperty('spaces');
    expect(res.body.spaces.map((s: any) => s.id)).toEqual([a.id]);
    expect(res.body.days['2026-06-10'][a.id].eventCount).toBe(1);
    expect(res.body.events.map((e: any) => e.title)).toEqual(['evt']);
  });

  it('only returns the viewer-visible spaces (never leaks others)', async () => {
    const mine = await repo.createSpace({ kind: 'case', title: 'Mine', ownerId: 'u1', visibility: 'private' });
    const theirs = await repo.createSpace({ kind: 'case', title: 'Theirs', ownerId: 'u1', visibility: 'private' });
    await repo.createCalendarEvent({ spaceId: mine.id, date: '2026-06-10', title: 'ok' });
    await repo.createCalendarEvent({ spaceId: theirs.id, date: '2026-06-10', title: 'secret' });

    const res = await request(makeApp(repo, stranger)).get('/api/calendar/cross?month=2026-06');
    expect(res.status).toBe(200);
    expect(res.body.spaces).toEqual([]);
    expect(res.body.events).toEqual([]);
    expect(res.body.days).toEqual({});
    // assert the secret never appears anywhere in the serialized payload
    expect(JSON.stringify(res.body)).not.toContain('secret');
    expect(JSON.stringify(res.body)).not.toContain(theirs.id);
  });

  it('bad month → 400', async () => {
    const res = await request(makeApp(repo, owner)).get('/api/calendar/cross?month=2026-6');
    expect(res.status).toBe(400);
  });

  it('missing month → 400', async () => {
    const res = await request(makeApp(repo, owner)).get('/api/calendar/cross');
    expect(res.status).toBe(400);
  });

  it('no-auth (synthetic admin) sees all spaces', async () => {
    const a = await repo.createSpace({ kind: 'case', title: 'A', ownerId: null, visibility: 'private' });
    await repo.createCalendarEvent({ spaceId: a.id, date: '2026-06-10', title: 'local-evt' });

    // no req.user → router falls back to synthetic 'local' admin (authActive=false)
    const res = await request(makeApp(repo, null)).get('/api/calendar/cross?month=2026-06');
    expect(res.status).toBe(200);
    expect(res.body.spaces.map((s: any) => s.id)).toEqual([a.id]);
    expect(res.body.events.map((e: any) => e.title)).toEqual(['local-evt']);
  });
});
