import { describe, it, expect } from 'vitest';

import { createCustomTheme, type CustomTheme } from './custom-theme';
import { createDataset, type Dataset } from './dataset';
import {
  EXPORT_ENVELOPE_VERSION,
  buildExportEnvelope,
  exportFilename,
  transferSummaryMessage,
  type ExportEnvelope,
} from './export-envelope';
import { createFontAsset, serializeFontAsset, type FontAsset } from './font-asset';
import { createSnippet, type Snippet } from './snippet';

const FIXED_NOW = new Date('2026-06-03T12:00:00.000Z');

function makeSnippet(overrides: Partial<Snippet> = {}): Snippet {
  return { ...createSnippet({ now: FIXED_NOW, id: 's1' }), ...overrides };
}

function makeTheme(overrides: Partial<CustomTheme> = {}): CustomTheme {
  return {
    ...createCustomTheme({ name: 'T1', config: { background: '#fff' }, now: FIXED_NOW }),
    id: 1,
    ...overrides,
  };
}

function makeDataset(overrides: Partial<Dataset> = {}): Dataset {
  return {
    ...createDataset({
      name: 'D1',
      data: 'a,b\n1,2',
      format: 'csv',
      source: 'inline',
      now: FIXED_NOW,
      id: 1,
    }),
    ...overrides,
  };
}

function makeFont(overrides: Partial<FontAsset> = {}): FontAsset {
  return {
    ...createFontAsset({
      family: 'Brand',
      data: new Uint8Array([1, 2, 3, 4]).buffer,
      format: 'woff2',
      fileName: 'brand.woff2',
      now: FIXED_NOW,
      id: 1,
    }),
    ...overrides,
  };
}

describe('EXPORT_ENVELOPE_VERSION', () => {
  it('is the spec-mandated "1.0"', () => {
    expect(EXPORT_ENVELOPE_VERSION).toBe('1.0');
  });
});

describe('buildExportEnvelope', () => {
  it('stamps version, ISO timestamp, and the fixed exporter tag', () => {
    const env = buildExportEnvelope([], [], [], [], { now: FIXED_NOW });
    expect(env.version).toBe('1.0');
    expect(env.exportedAt).toBe('2026-06-03T12:00:00.000Z');
    expect(env.exportedBy).toBe('Astrolabe');
  });

  it('produces the full envelope shape (matches spec §08 example fields)', () => {
    const snippet = makeSnippet();
    const dataset = makeDataset();
    const theme = makeTheme();
    const font = makeFont();
    const env = buildExportEnvelope([snippet], [dataset], [theme], [font], { now: FIXED_NOW });
    const expected: ExportEnvelope = {
      version: '1.0',
      exportedAt: '2026-06-03T12:00:00.000Z',
      exportedBy: 'Astrolabe',
      snippets: [snippet],
      datasets: [dataset],
      themes: [theme],
      fonts: [serializeFontAsset(font)],
    };
    expect(env).toEqual(expected);
  });

  it('carries the records through unchanged', () => {
    const snippet = makeSnippet();
    const dataset = makeDataset();
    const theme = makeTheme();
    const env = buildExportEnvelope([snippet], [dataset], [theme], [], { now: FIXED_NOW });
    expect(env.snippets[0]).toBe(snippet);
    expect(env.datasets[0]).toBe(dataset);
    expect(env.themes[0]).toBe(theme);
  });

  it('serializes font bytes to base64 (the array is JSON-safe)', () => {
    const font = makeFont();
    const env = buildExportEnvelope([makeSnippet()], [], [], [font], { now: FIXED_NOW });
    expect(typeof env.fonts[0].data).toBe('string');
    // The whole envelope must survive a JSON round-trip without loss.
    expect(() => JSON.stringify(env)).not.toThrow();
    expect(env.fonts[0].family).toBe('Brand');
    expect(env.fonts[0].size).toBe(4);
  });

  it("preserves each record's own version field", () => {
    const snippet = makeSnippet({ version: 1 });
    const dataset = makeDataset({ version: 1 });
    const theme = makeTheme({ version: 1 });
    const env = buildExportEnvelope([snippet], [dataset], [theme], [], { now: FIXED_NOW });
    expect(env.snippets[0].version).toBe(1);
    expect(env.datasets[0].version).toBe(1);
    expect(env.themes[0].version).toBe(1);
    // The per-record version is distinct from the envelope's file-format version.
    expect(env.version).toBe('1.0');
  });

  it("does not alias the caller's arrays (mutating inputs afterward is inert)", () => {
    const snippets = [makeSnippet()];
    const datasets = [makeDataset()];
    const themes = [makeTheme()];
    const fonts = [makeFont()];
    const env = buildExportEnvelope(snippets, datasets, themes, fonts, { now: FIXED_NOW });

    snippets.push(makeSnippet({ id: 's2' }));
    datasets.push(makeDataset({ id: 2, name: 'D2' }));
    themes.push(makeTheme({ id: 2, name: 'T2' }));
    fonts.push(makeFont({ id: 2, family: 'Brand 2' }));

    expect(env.snippets).toHaveLength(1);
    expect(env.datasets).toHaveLength(1);
    expect(env.themes).toHaveLength(1);
    expect(env.fonts).toHaveLength(1);
  });

  it('handles empty arrays', () => {
    const env = buildExportEnvelope([], [], [], [], { now: FIXED_NOW });
    expect(env.snippets).toEqual([]);
    expect(env.datasets).toEqual([]);
    expect(env.themes).toEqual([]);
    expect(env.fonts).toEqual([]);
  });
});

describe('exportFilename', () => {
  it('formats as astrolabe-project-YYYY-MM-DD.json with zero-padding', () => {
    // Local date parts; construct with local Y/M/D to avoid TZ ambiguity.
    expect(exportFilename(new Date(2026, 0, 5))).toBe('astrolabe-project-2026-01-05.json');
  });

  it('zero-pads two-digit month and day', () => {
    expect(exportFilename(new Date(2026, 11, 25))).toBe('astrolabe-project-2026-12-25.json');
  });
});

describe('transferSummaryMessage (shared by export and import feedback)', () => {
  it('reports snippet and dataset counts (plural)', () => {
    expect(transferSummaryMessage('Exported', 4, 2, 0, 0)).toBe(
      'Exported 4 snippets and 2 datasets',
    );
  });

  it('omits the dataset, theme, and font clauses when their counts are zero', () => {
    expect(transferSummaryMessage('Exported', 4, 0, 0, 0)).toBe('Exported 4 snippets');
  });

  it('uses singular wording for counts of 1', () => {
    expect(transferSummaryMessage('Imported', 1, 1, 0, 0)).toBe('Imported 1 snippet and 1 dataset');
  });

  it('pluralizes zero counts (and omits the other clauses)', () => {
    expect(transferSummaryMessage('Imported', 0, 0, 0, 0)).toBe('Imported 0 snippets');
  });

  it('reports all four counts with comma-and joining', () => {
    expect(transferSummaryMessage('Exported', 4, 2, 1, 3)).toBe(
      'Exported 4 snippets, 2 datasets, 1 theme and 3 fonts',
    );
  });

  it('joins snippets and fonts with "and" when there are no datasets or themes', () => {
    expect(transferSummaryMessage('Imported', 4, 0, 0, 1)).toBe('Imported 4 snippets and 1 font');
  });
});
