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

import {
  ProjectValidationError,
  assertValidProject,
  validateProject,
} from '../../../src/project/project.validation';
import { populatedProject } from './project-fixture';

describe('project validation', () => {
  it('returns an isolated, typed copy for a valid version-1 project', () => {
    const input = populatedProject();
    const result = validateProject(input);

    expect(result.valid).toBe(true);
    if (result.valid) {
      expect(result.project).toEqual(input);
      expect(result.project).not.toBe(input);
    }
  });

  it('reports duplicate identities, missing references and bad ranges', () => {
    const project = populatedProject();
    const invalid = {
      ...project,
      assets: [...project.assets, { ...project.assets[0] }],
      timeline: [
        {
          ...project.timeline[0],
          assetId: 'missing',
          sourceInSeconds: 5,
          sourceOutSeconds: 3,
          crop: { x: -1, y: 0, width: 0, height: 100 },
        },
      ],
    };

    const result = validateProject(invalid);
    expect(result.valid).toBe(false);
    if (!result.valid) {
      expect(result.issues.map((issue) => issue.code)).toEqual(
        expect.arrayContaining([
          'duplicate-id',
          'missing-reference',
          'invalid-range',
          'invalid-value',
        ])
      );
    }
  });

  it('checks clip bounds, fade bounds, chapter bounds and output streams', () => {
    const project = populatedProject();
    const invalid = {
      ...project,
      timeline: [
        {
          ...project.timeline[0],
          sourceOutSeconds: 11,
          audio: {
            enabled: true,
            fadeInSeconds: 8,
            fadeOutSeconds: 8,
          },
        },
      ],
      chapters: [
        {
          id: 'chapter-1',
          startSeconds: 0,
          endSeconds: 20,
          title: 'Too long',
        },
      ],
      output: {
        ...project.output,
        videoEnabled: false,
        audioEnabled: false,
      },
    };
    const result = validateProject(invalid);

    expect(result.valid).toBe(false);
    if (!result.valid) {
      expect(result.issues).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            code: 'invalid-range',
            path: 'timeline[0]',
          }),
          expect.objectContaining({
            code: 'out-of-bounds',
            path: 'timeline[0].audio',
          }),
          expect.objectContaining({
            code: 'out-of-bounds',
            path: 'chapters[0].endSeconds',
          }),
          expect.objectContaining({
            code: 'invalid-value',
            path: 'output',
          }),
        ])
      );
    }
  });

  it('validates persisted fade curves and chapter time-base resolution', () => {
    const project = populatedProject();
    const invalid = {
      ...project,
      timeline: [
        {
          ...project.timeline[0],
          audio: {
            enabled: true,
            fadeInSeconds: 0.2,
            fadeCurve: 'log',
          },
        },
      ],
      chapters: [
        {
          id: 'chapter-1',
          startSeconds: 0,
          endSeconds: 0.1,
          title: 'Too short for this base',
          timeBase: '1/1',
        },
      ],
    };

    const result = validateProject(invalid);
    expect(result.valid).toBe(false);
    if (!result.valid) {
      expect(result.issues).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            code: 'invalid-value',
            path: 'timeline[0].audio.fadeCurve',
          }),
          expect.objectContaining({
            code: 'invalid-value',
            path: 'chapters[0].timeBase',
          }),
        ])
      );
    }
  });

  it('rejects unsupported schema versions with a useful typed exception', () => {
    const project = { ...populatedProject(), schemaVersion: 99 };
    expect(() => assertValidProject(project)).toThrow(ProjectValidationError);
    const result = validateProject(project);
    expect(result.valid).toBe(false);
    if (!result.valid) {
      expect(result.issues[0]).toMatchObject({
        code: 'unsupported-version',
        path: 'schemaVersion',
      });
    }
  });

  it('does not trust malformed normalized probe fields from an import', () => {
    const project = populatedProject();
    const invalid = structuredClone(project) as unknown as {
      assets: Array<{
        probe?: {
          streams: Array<{
            width?: unknown;
            rotationDegrees?: unknown;
          }>;
        };
      }>;
    };
    const stream = invalid.assets[0]?.probe?.streams[0];
    if (stream) {
      stream.width = '1920';
      stream.rotationDegrees = '90';
    }

    const result = validateProject(invalid);
    expect(result.valid).toBe(false);
    if (!result.valid) {
      expect(result.issues).toContainEqual(
        expect.objectContaining({
          code: 'invalid-type',
          path: 'assets[0].probe.streams[0].width',
        })
      );
      expect(result.issues).toContainEqual(
        expect.objectContaining({
          code: 'invalid-type',
          path: 'assets[0].probe.streams[0].rotationDegrees',
        })
      );
    }
  });
});
