import { describe, it, expect, vi, beforeEach } from 'vitest';
import { join, sep } from 'node:path';

const { mockReadFile, mockHandle, mockRegisterSchemes } = vi.hoisted(() => ({
  mockReadFile: vi.fn(),
  mockHandle: vi.fn(),
  mockRegisterSchemes: vi.fn()
}));

vi.mock('node:fs/promises', () => ({
  readFile: mockReadFile
}));

vi.mock('electron', () => ({
  default: {
    protocol: {
      registerSchemesAsPrivileged: mockRegisterSchemes,
      handle: mockHandle
    }
  }
}));

import { registerInklingMediaProtocol, inferMime } from '../../src/main/protocol/inklingMedia';

describe('inferMime', () => {
  it('returns image/png for .png', () => { expect(inferMime('foo.png')).toBe('image/png'); });
  it('returns image/jpeg for .jpg and .jpeg', () => {
    expect(inferMime('foo.jpg')).toBe('image/jpeg');
    expect(inferMime('foo.jpeg')).toBe('image/jpeg');
  });
  it('returns image/gif for .gif', () => { expect(inferMime('foo.gif')).toBe('image/gif'); });
  it('returns image/webp for .webp', () => { expect(inferMime('foo.webp')).toBe('image/webp'); });
  it('returns application/octet-stream for unknown', () => { expect(inferMime('foo.xyz')).toBe('application/octet-stream'); });
});

describe('inkling-media protocol handler', () => {
  beforeEach(() => { vi.clearAllMocks(); });

  function getHandler(profileDir: string): (req: { url: string }) => Promise<Response> {
    registerInklingMediaProtocol(profileDir);
    const call = mockHandle.mock.calls[0];
    if (!call) throw new Error('protocol.handle not called');
    return call[1] as (req: { url: string }) => Promise<Response>;
  }

  // 실 운영 (Electron protocol.handle) 에서는 req.url 이 raw 문자열로 전달되지만,
  // vitest 의 `new Request()` constructor 는 url 을 즉시 normalize (`/../` 제거) 함.
  // 따라서 traversal 검사 로직이 raw URL 단계에서 작동하는지 검증하려면
  // raw url 을 보존한 minimal mock 을 직접 전달.
  function rawReq(url: string): { url: string } { return { url }; }

  it('serves valid file with correct mime', async () => {
    mockReadFile.mockResolvedValueOnce(Buffer.from([1, 2, 3]));
    const handler = getHandler('/profile');
    const res = await handler(rawReq('inkling-media://media/note1/img.png'));
    expect(res.status).toBe(200);
    expect(res.headers.get('content-type')).toBe('image/png');
    expect(mockReadFile).toHaveBeenCalledWith(join('/profile', 'media', 'note1', 'img.png'));
  });

  it('returns 403 on path traversal attempt', async () => {
    const handler = getHandler('/profile');
    const res = await handler(rawReq('inkling-media://media/../etc/passwd'));
    expect(res.status).toBe(403);
    expect(mockReadFile).not.toHaveBeenCalled();
  });

  it('returns 404 when file missing', async () => {
    mockReadFile.mockRejectedValueOnce(new Error('ENOENT'));
    const handler = getHandler('/profile');
    const res = await handler(rawReq('inkling-media://media/note1/missing.png'));
    expect(res.status).toBe(404);
  });
});
