import { describe, it, expect, vi, beforeEach } from 'vitest';
import { parseScript, serializeScript } from './frontmatter.js';
import type { ParsedScript } from './frontmatter.js';

// ── helpers ──────────────────────────────────────────────────────────────────

const VALID_SOURCE = `---
description: "Log into example.com"
params:
  - name: date
    type: string
    description: "ISO date YYYY-MM-DD"
  - name: verbose
    type: boolean
    default: false
session_profile_id: 7
recording_source: "rec-2026-05-09T12-34-56.json"
created_at: "2026-05-09T12:35:01Z"
updated_at: "2026-05-09T12:35:01Z"
---

async function main({ context, params }) {}
module.exports = main;
`;

const VALID_PARSED: ParsedScript = {
  frontmatter: {
    description: 'Log into example.com',
    params: [
      { name: 'date', type: 'string', description: 'ISO date YYYY-MM-DD' },
      { name: 'verbose', type: 'boolean', default: false },
    ],
    sessionProfileId: 7,
    recordingSource: 'rec-2026-05-09T12-34-56.json',
    createdAt: '2026-05-09T12:35:01Z',
    updatedAt: '2026-05-09T12:35:01Z',
  },
  body: 'async function main({ context, params }) {}\nmodule.exports = main;\n',
};

// ── tests ─────────────────────────────────────────────────────────────────────

describe('user-folder/frontmatter', () => {
  // Test 1: valid input produces expected ParsedScript
  it('parseScript of a valid input produces the expected ParsedScript', () => {
    const result = parseScript(VALID_SOURCE);
    expect(result).toEqual(VALID_PARSED);
  });

  // Test 2: body-only input (no frontmatter)
  it('parseScript of body-only input produces { frontmatter: { description: "", params: [] }, body }', () => {
    const body = 'async function main() {}\n';
    const result = parseScript(body);
    expect(result).toEqual({
      frontmatter: { description: '', params: [] },
      body,
    });
  });

  // Test 3: malformed YAML throws
  it('parseScript of malformed YAML throws', () => {
    const source = `---
description: [unclosed
---
body
`;
    expect(() => parseScript(source)).toThrow();
  });

  // Test 4: bad params[i].name throws with index in message
  it('parseScript of frontmatter with bad params[i].name throws with "params[i].name" in message', () => {
    const source = `---
description: test
params:
  - name: valid
    type: string
  - name: "1-invalid"
    type: string
---
body
`;
    expect(() => parseScript(source)).toThrow(/params\[1\]\.name/);
  });

  // Test 5: bad params[i].type throws with type info in message
  it('parseScript of frontmatter with bad params[i].type throws with "params[i].type" in message', () => {
    const source = `---
description: test
params:
  - name: myParam
    type: integer
---
body
`;
    expect(() => parseScript(source)).toThrow(/params\[0\]\.type/);
  });

  // Test 6: session_profile_id: 0 throws (not positive)
  it('parseScript of frontmatter with session_profile_id: 0 throws', () => {
    const source = `---
description: test
session_profile_id: 0
---
body
`;
    expect(() => parseScript(source)).toThrow(/session_profile_id/);
  });

  // Test 7: session_profile_id: -1 throws
  it('parseScript of frontmatter with session_profile_id: -1 throws', () => {
    const source = `---
description: test
session_profile_id: -1
---
body
`;
    expect(() => parseScript(source)).toThrow(/session_profile_id/);
  });

  // Test 8: session_profile_id: 1.5 throws (not integer)
  it('parseScript of frontmatter with session_profile_id: 1.5 throws', () => {
    const source = `---
description: test
session_profile_id: 1.5
---
body
`;
    expect(() => parseScript(source)).toThrow(/session_profile_id/);
  });

  // Test 8b: session_profile_id: true throws (not a number)
  it('throws when session_profile_id is a boolean', () => {
    const src = '---\ndescription: test\nparams: []\nsession_profile_id: true\n---\n';
    expect(() => parseScript(src)).toThrow(/session_profile_id/);
  });

  // Test 9: unknown keys warn via logger.warn
  it('parseScript accepts unknown keys and calls logger.warn', async () => {
    const loggerModule = await import('../logger.js');
    const warnSpy = vi.spyOn(loggerModule.logger, 'warn').mockImplementation(() => {});
    try {
      const source = `---
description: test
unknown_field: "some value"
another_unknown: 42
---
body
`;
      const result = parseScript(source);
      expect(result.frontmatter.description).toBe('test');
      expect(warnSpy).toHaveBeenCalled();
    } finally {
      warnSpy.mockRestore();
    }
  });

  // Test 10: round-trip
  it('serializeScript round-trip: parseScript(serializeScript(s)) equals s', () => {
    const serialized = serializeScript(VALID_PARSED);
    const reparsed = parseScript(serialized);
    expect(reparsed).toEqual(VALID_PARSED);
  });

  // Test 11: empty params array does NOT emit a params: key
  it('serializeScript with empty params array does NOT emit a params: key', () => {
    const script: ParsedScript = {
      frontmatter: { description: 'minimal', params: [] },
      body: 'const x = 1;\n',
    };
    const serialized = serializeScript(script);
    expect(serialized).not.toContain('params:');
  });

  // Test 12: omits undefined optional fields
  it('serializeScript omits undefined optional fields', () => {
    const script: ParsedScript = {
      frontmatter: {
        description: 'no optionals',
        params: [],
      },
      body: 'const x = 1;\n',
    };
    const serialized = serializeScript(script);
    expect(serialized).not.toContain('session_profile_id');
    expect(serialized).not.toContain('recording_source');
    expect(serialized).not.toContain('created_at');
    expect(serialized).not.toContain('updated_at');
  });
});
