import { describe, expect, it } from 'vitest';
import {
  createExportReport,
  deriveOutputFileName,
  exportReportBlob,
  isBrowserPreviewMimeType,
  isSafeArchiveLeafName,
  mimeTypeFromOutputName,
  normalizeOutputMimeType,
  preferredExtensionForMimeType,
  redactPrivatePaths,
  safeExportFileName,
  serializeExportReport,
  uniqueExportFileNames,
  type ExportResult,
} from '../../src/export';

describe('safe export output names', () => {
  it('removes directory traversal, device names, and unsafe punctuation', () => {
    expect(safeExportFileName('../../CON.mp4')).toBe('_CON.mp4');
    expect(safeExportFileName('..\\..\\holiday 🎬.MP4')).toBe('holiday.mp4');
    expect(
      safeExportFileName('/private/video.mov', { extension: '.webm' })
    ).toBe('video.webm');
    expect(isSafeArchiveLeafName('../unsafe.mp4')).toBe(false);
    expect(isSafeArchiveLeafName('safe-output.mp4')).toBe(true);
  });

  it('derives numbered names and rejects invalid part positions', () => {
    expect(
      deriveOutputFileName({
        sourceName: 'My recording.mov',
        operation: 'split',
        extension: 'mp4',
        partIndex: 1,
        partCount: 12,
      })
    ).toBe('My-recording-split-002.mp4');
    expect(() =>
      deriveOutputFileName({
        sourceName: 'source.mp4',
        operation: 'split',
        partIndex: 2,
        partCount: 2,
      })
    ).toThrow(/part index/iu);
  });

  it('deduplicates names for case-insensitive filesystems', () => {
    expect(
      uniqueExportFileNames([
        'Clip.mp4',
        'clip.mp4',
        '../../CLIP.mp4',
        'other.mp4',
      ])
    ).toEqual(['Clip.mp4', 'clip-2.mp4', 'CLIP-3.mp4', 'other.mp4']);
  });
});

describe('output MIME helpers', () => {
  it('normalizes base types and falls back to known extensions', () => {
    expect(normalizeOutputMimeType('Video/MP4; codecs=avc1')).toBe('video/mp4');
    expect(normalizeOutputMimeType('not a mime', 'frame.png')).toBe(
      'image/png'
    );
    expect(normalizeOutputMimeType(undefined, 'unknown.xyz')).toBe(
      'application/octet-stream'
    );
    expect(mimeTypeFromOutputName('captions.vtt')).toBe('text/vtt');
    expect(preferredExtensionForMimeType('image/jpeg')).toBe('.jpg');
    expect(isBrowserPreviewMimeType('audio/mpeg')).toBe(true);
    expect(isBrowserPreviewMimeType('application/json')).toBe(false);
  });
});

describe('privacy-safe JSON export reports', () => {
  const result = {
    id: 'result',
    planId: 'plan',
    outputId: 'media',
    fileName: '../../converted.mp4',
    blob: new Blob(['private bytes']),
    mimeType: 'video/mp4',
    role: 'media',
    size: 13,
    createdAt: '2026-01-01T00:00:00.000Z',
  } satisfies ExportResult;

  it('includes plan diagnostics and output metadata without source data', async () => {
    const privateRuntimeResult = {
      ...result,
      // Extra runtime fields must never be copied into the report.
      sourceBlob: new Blob(['source secret']),
      hostPath: '/home/zemion/private/source.mp4',
    };
    const report = createExportReport({
      plan: {
        id: 'plan-/home/zemion/private/source.mp4',
        operation: 'convert',
        diagnostics: [
          {
            code: 'codec-warning',
            severity: 'warning',
            message:
              'Read /mnt/DATA/Videos/secret.mov via blob:https://local/id',
            field: 'C:\\Users\\Person\\Videos\\secret.mov',
          },
        ],
      },
      results: [privateRuntimeResult],
      execution: {
        status: 'completed',
        engineMode: 'single-thread',
        ffmpegVersion: '6.0',
        elapsedSeconds: 1.25,
        conversionMilliseconds: 1_000,
        outputReadMilliseconds: 25,
        cleanupMilliseconds: 5,
        exitCode: 0,
      },
      generatedAt: '2026-07-24T12:00:00+02:00',
    });

    expect(report.generatedAt).toBe('2026-07-24T10:00:00.000Z');
    expect(report.plan.id).toContain('[local path]');
    expect(report.outputs[0]).toEqual({
      id: 'result',
      outputId: 'media',
      fileName: 'converted.mp4',
      mimeType: 'video/mp4',
      role: 'media',
      sizeBytes: 13,
    });
    expect(report.diagnostics[0]?.message).toBe(
      'Read [local path] via [object URL]'
    );
    expect(report.diagnostics[0]?.field).toBe('[local path]');
    expect(report.privacy).toEqual({
      includesSourceMedia: false,
      includesCommandArguments: false,
      privatePathsRedacted: true,
    });
    expect(report.execution).toMatchObject({
      conversionMilliseconds: 1_000,
      outputReadMilliseconds: 25,
      cleanupMilliseconds: 5,
    });

    const serialized = serializeExportReport(report);
    expect(serialized.endsWith('\n')).toBe(true);
    expect(serialized).not.toContain('sourceBlob');
    expect(serialized).not.toContain('hostPath');
    expect(serialized).not.toContain('/home/zemion');
    const blob = exportReportBlob(report);
    expect(blob.type).toBe('application/json');
    expect(await blob.text()).toBe(serialized);
  });

  it('redacts common local path and URL forms', () => {
    expect(
      redactPrivatePaths(
        'file:///home/me/a.mp4 C:\\fakepath\\b.mp4 /Users/me/c.mp4'
      )
    ).toBe('[local path] [local path] [local path]');
  });

  it('rejects invalid numeric report metadata', () => {
    expect(() =>
      createExportReport({
        plan: { id: 'plan', operation: 'convert', diagnostics: [] },
        results: [{ ...result, size: Number.NaN }],
      })
    ).toThrow(/output size/iu);
  });
});
