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

import {
  ProjectImportError,
  importProjectDocument,
  projectDocumentFileName,
  serializeProject,
} from '../../../src/project/project.serialization';
import { ProjectValidationError } from '../../../src/project/project.validation';
import { populatedProject } from './project-fixture';

describe('project serialization', () => {
  it('serializes readable, versioned JSON and can omit transient UI state', () => {
    const project = populatedProject();
    const json = serializeProject(project, { includeUiState: false });
    const parsed = JSON.parse(json) as Record<string, unknown>;

    expect(json.endsWith('\n')).toBe(true);
    expect(parsed.schemaVersion).toBe(1);
    expect(parsed.ui).toBeUndefined();
    expect(project.ui).toBeDefined();
  });

  it('validates before serializing', () => {
    const project = {
      ...populatedProject(),
      output: { ...populatedProject().output, fileName: '' },
    };
    expect(() => serializeProject(project)).toThrow(ProjectValidationError);
  });

  it('imports a round trip but correctly marks browser sources missing', () => {
    const project = populatedProject();
    const result = importProjectDocument(serializeProject(project));

    expect(result.fromVersion).toBe(1);
    expect(result.project.assets[0]?.sourceStatus).toBe('missing');
    expect(result.warnings).toContainEqual(
      expect.objectContaining({ code: 'source-reattachment-required' })
    );
  });

  it('can retain runtime source status only when explicitly requested', () => {
    const project = populatedProject();
    const result = importProjectDocument(serializeProject(project), {
      markSourcesMissing: false,
    });
    expect(result.project).toEqual(project);
    expect(result.warnings).toEqual([]);
  });

  it('round-trips fade curves and editable chapter time bases', () => {
    const project = populatedProject();
    const persisted = {
      ...project,
      timeline: [
        {
          ...project.timeline[0]!,
          audio: {
            enabled: true,
            fadeInSeconds: 0.2,
            fadeCurve: 'qsin' as const,
          },
          video: {
            enabled: true,
            fadeOutSeconds: 0.2,
            fadeCurve: 'exp' as const,
          },
        },
      ],
      chapters: [
        {
          id: 'chapter-1',
          title: 'Intro',
          startSeconds: 0,
          endSeconds: 1,
          timeBase: '1/48000',
        },
      ],
    };
    const result = importProjectDocument(serializeProject(persisted), {
      markSourcesMissing: false,
    });

    expect(result.project.timeline[0]?.audio?.fadeCurve).toBe('qsin');
    expect(result.project.timeline[0]?.video?.fadeCurve).toBe('exp');
    expect(result.project.chapters[0]?.timeBase).toBe('1/48000');
  });

  it('still reports reattachment when an exported project was already missing sources', () => {
    const project = {
      ...populatedProject(),
      assets: [
        { ...populatedProject().assets[0]!, sourceStatus: 'missing' as const },
      ],
    };
    const result = importProjectDocument(serializeProject(project));
    expect(result.warnings).toContainEqual(
      expect.objectContaining({ code: 'source-reattachment-required' })
    );
  });

  it('migrates the documented legacy version zero shape', () => {
    const legacy = {
      schemaVersion: 0,
      id: 'legacy-project',
      title: 'Legacy project',
      createdAt: '2025-01-01T00:00:00.000Z',
      updatedAt: '2025-01-01T00:00:00.000Z',
      media: [
        {
          id: 'legacy-source',
          fileName: 'legacy.mp4',
          size: 42,
          lastModified: 100,
          type: 'video/mp4',
          sourceStatus: 'attached',
        },
      ],
      clips: [
        {
          id: 'legacy-clip',
          assetId: 'legacy-source',
          inSeconds: 0,
          outSeconds: 2,
        },
      ],
    };
    const result = importProjectDocument(JSON.stringify(legacy));

    expect(result.fromVersion).toBe(0);
    expect(result.project).toMatchObject({
      schemaVersion: 1,
      name: 'Legacy project',
      assets: [
        {
          originalName: 'legacy.mp4',
          mimeType: 'video/mp4',
          sourceStatus: 'missing',
        },
      ],
      timeline: [
        {
          sourceInSeconds: 0,
          sourceOutSeconds: 2,
        },
      ],
    });
    expect(result.warnings.map((warning) => warning.code)).toEqual(
      expect.arrayContaining([
        'renamed-field',
        'defaulted-field',
        'source-reattachment-required',
      ])
    );
  });

  it('rejects malformed, unversioned, future and oversized documents', () => {
    expect(() => importProjectDocument('{')).toThrow(ProjectImportError);
    expect(() => importProjectDocument('{}')).toThrow(
      'does not declare a schema version'
    );
    expect(() => importProjectDocument('{"schemaVersion":999}')).toThrow(
      'is not supported'
    );
    expect(() =>
      importProjectDocument('{"schemaVersion":1}', { maximumBytes: 5 })
    ).toThrow('exceeds');
  });

  it('creates a safe project-document filename', () => {
    expect(
      projectDocumentFileName({
        ...populatedProject(),
        name: '../../My Project',
      })
    ).toBe('My-Project.avproject.json');
  });
});
