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

import { transformDocument } from '@/lib/document';
import type { DocumentArtifact, DocumentTransform } from '@/lib/document';

function artifact(text = 'Original'): DocumentArtifact {
  return {
    metadata: { fileName: 'manual.md', mimeType: 'text/markdown' },
    blocks: [{ id: 'text_1', type: 'markdown', text }],
    assets: [],
  };
}

const context = {
  purpose: 'course-generation' as const,
  budget: { maxTextChars: 50_000, maxVisionImages: 20 },
};

describe('document transform pipeline', () => {
  it('runs transforms in order, records lineage, metrics, and keeps the input immutable', async () => {
    const input = artifact();
    const append = (id: string, suffix: string): DocumentTransform => ({
      id,
      displayName: id,
      version: '1.0.0',
      capabilities: {},
      apply(current) {
        current.blocks[0].text += suffix;
        return { artifact: current };
      },
    });

    const result = await transformDocument(
      input,
      [append('first', ' A'), append('second', ' B')],
      context,
    );

    expect(input.blocks[0].text).toBe('Original');
    expect(result.artifact.blocks[0].text).toBe('Original A B');
    expect(result.artifact.transforms?.map((record) => record.transformId)).toEqual([
      'first',
      'second',
    ]);
    expect(result.artifact.transforms?.every((record) => record.status === 'applied')).toBe(true);
    expect(result.metrics).toMatchObject({
      inputChars: 8,
      outputChars: 12,
      inputAssets: 0,
      outputAssets: 0,
    });
  });

  it('continues after a failed transform in best-effort mode and records the failure', async () => {
    const failing: DocumentTransform = {
      id: 'failing',
      displayName: 'Failing',
      version: '1.0.0',
      capabilities: {},
      apply() {
        throw new Error('broken input');
      },
    };
    const following: DocumentTransform = {
      id: 'following',
      displayName: 'Following',
      version: '1.0.0',
      capabilities: {},
      apply(current) {
        current.blocks[0].text = 'Recovered';
        return { artifact: current };
      },
    };

    const result = await transformDocument(artifact(), [failing, following], context, {
      failurePolicy: 'best-effort',
    });

    expect(result.artifact.blocks[0].text).toBe('Recovered');
    expect(result.artifact.transforms?.map((record) => record.status)).toEqual([
      'failed',
      'applied',
    ]);
    expect(result.diagnostics[0].message).toContain('broken input');
  });

  it('fails fast by default and does not run later transforms', async () => {
    const laterApply = vi.fn();
    const transforms: DocumentTransform[] = [
      {
        id: 'failing',
        displayName: 'Failing',
        version: '1.0.0',
        capabilities: {},
        apply() {
          throw new Error('stop');
        },
      },
      {
        id: 'later',
        displayName: 'Later',
        version: '1.0.0',
        capabilities: {},
        apply(current) {
          laterApply();
          return { artifact: current };
        },
      },
    ];

    await expect(transformDocument(artifact(), transforms, context)).rejects.toThrow('stop');
    expect(laterApply).not.toHaveBeenCalled();
  });

  it('honors an already-aborted signal', async () => {
    const controller = new AbortController();
    controller.abort();

    await expect(
      transformDocument(artifact(), [], { ...context, signal: controller.signal }),
    ).rejects.toMatchObject({ name: 'AbortError' });
  });

  it('preserves pipeline-owned history when a transform rebuilds the artifact', async () => {
    const rebuild: DocumentTransform = {
      id: 'rebuild',
      displayName: 'Rebuild',
      version: '1.0.0',
      capabilities: {},
      apply(current) {
        return {
          artifact: {
            metadata: { ...current.metadata },
            blocks: current.blocks.map((block) => ({ ...block, text: `${block.text} rebuilt` })),
            assets: [],
          },
        };
      },
    };
    const first: DocumentTransform = {
      id: 'first',
      displayName: 'First',
      version: '1.0.0',
      capabilities: {},
      apply(current) {
        return { artifact: current };
      },
    };

    const result = await transformDocument(artifact(), [first, rebuild], context);

    expect(result.artifact.transforms?.map((record) => record.transformId)).toEqual([
      'first',
      'rebuild',
    ]);
  });
});