/**
 * Integration test: GET /api/storage/[token]
 *
 * Exercises the §14.9a critical mitigations on the live route:
 *   - HMAC verification: a token signed with the wrong secret is rejected.
 *   - Expiry: an expired token is rejected.
 *   - Single-use replay: a token used twice (within the replay TTL) is
 *     rejected the second time.
 *   - Happy path: a valid token streams the file with correct headers.
 *
 * The storage backend itself is mocked to a FilesystemBackend rooted in a
 * tempdir. Redis is mocked to an in-memory map so the test doesn't need
 * a live Redis.
 */

import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import * as path from 'node:path';

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

const VALID_KEY = 'a'.repeat(64);

// Hoisted in-memory Redis. The proxy route uses SET NX EX, so we model
// just enough behaviour to track keys that have been seen.
const redisStore = new Map<string, string>();

vi.mock('@/lib/redis', () => ({
  redis: {
    set: vi.fn(async (key: string, value: string, ..._args: unknown[]) => {
      // _args = ['EX', ttl, 'NX'] in our usage. Honour NX semantics.
      const nxIndex = _args.findIndex((a) => a === 'NX');
      if (nxIndex >= 0 && redisStore.has(key)) return null;
      redisStore.set(key, value);
      return 'OK';
    }),
  },
}));

vi.mock('@/lib/logger', () => ({
  logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
}));

beforeAll(() => {
  process.env.EMAIL_CREDENTIAL_KEY = VALID_KEY;
  process.env.BETTER_AUTH_SECRET = 'a'.repeat(64);
});

describe('GET /api/storage/[token]', () => {
  let storageRoot: string;
  let backend: import('@/lib/storage/filesystem').FilesystemBackend;
  let getMock: ReturnType<typeof vi.fn>;

  beforeEach(async () => {
    redisStore.clear();
    storageRoot = await mkdtemp(path.join(tmpdir(), 'pn-storage-route-'));

    // Use the real FilesystemBackend so the resolution / realpath logic is
    // genuinely exercised; mock just `getStorageBackend()` to return it.
    const { FilesystemBackend } = await import('@/lib/storage/filesystem');
    backend = await FilesystemBackend.create({
      root: storageRoot,
      proxyHmacSecretEncrypted: null,
    });

    getMock = vi.fn(async () => backend);
    vi.doMock('@/lib/storage', async () => {
      const real = await vi.importActual<typeof import('@/lib/storage')>('@/lib/storage');
      return { ...real, getStorageBackend: getMock };
    });
  });

  afterEach(async () => {
    vi.doUnmock('@/lib/storage');
    await rm(storageRoot, { recursive: true, force: true });
  });

  async function callRoute(token: string) {
    const { GET } = await import('@/app/api/storage/[token]/route');
    return GET(new Request(`http://test/api/storage/${token}`) as never, {
      params: Promise.resolve({ token }),
    });
  }

  it('serves a file with a valid token (happy path)', async () => {
    await backend.put('berths/abc/file.txt', Buffer.from('hello world'), {
      contentType: 'text/plain',
    });
    const presigned = await backend.presignDownload('berths/abc/file.txt', {
      expirySeconds: 60,
      filename: 'file.txt',
      contentType: 'text/plain',
    });
    const token = presigned.url.split('/api/storage/').pop()!;

    const res = await callRoute(token);
    expect(res.status).toBe(200);
    expect(res.headers.get('Content-Type')).toBe('text/plain');
    expect(res.headers.get('X-Content-Type-Options')).toBe('nosniff');
    const text = await res.text();
    expect(text).toBe('hello world');
  });

  it('rejects a token signed with the wrong HMAC secret', async () => {
    await backend.put('berths/abc/file.txt', Buffer.from('hello'), {
      contentType: 'text/plain',
    });
    const { signProxyToken } = await import('@/lib/storage/filesystem');
    const badToken = signProxyToken(
      {
        k: 'berths/abc/file.txt',
        e: Math.floor(Date.now() / 1000) + 60,
        n: 'nonce',
        op: 'get' as const,
      },
      'wrong-secret',
    );
    const res = await callRoute(badToken);
    expect(res.status).toBe(403);
    const body = (await res.json()) as any;
    expect(body.error).toMatch(/Invalid|expired/i);
  });

  it('rejects an expired token', async () => {
    await backend.put('berths/abc/file.txt', Buffer.from('hello'), {
      contentType: 'text/plain',
    });
    const { signProxyToken } = await import('@/lib/storage/filesystem');
    const expiredToken = signProxyToken(
      {
        k: 'berths/abc/file.txt',
        e: Math.floor(Date.now() / 1000) - 1,
        n: 'nonce',
        op: 'get' as const,
      },
      backend.getHmacSecret(),
    );
    const res = await callRoute(expiredToken);
    expect(res.status).toBe(403);
  });

  it('refuses to replay a token a second time within the TTL', async () => {
    await backend.put('berths/abc/file.txt', Buffer.from('hello'), {
      contentType: 'text/plain',
    });
    const presigned = await backend.presignDownload('berths/abc/file.txt', {
      expirySeconds: 60,
    });
    const token = presigned.url.split('/api/storage/').pop()!;

    const first = await callRoute(token);
    expect(first.status).toBe(200);
    await first.text();

    const second = await callRoute(token);
    expect(second.status).toBe(403);
    const body = (await second.json()) as any;
    expect(body.error).toMatch(/already used/i);
  });
});
