import { BadRequestException, NotFoundException } from '@nestjs/common';
import { AiFormService } from './ai-form.service';

function createService(overrides: Record<string, unknown> = {}) {
  const forms = {
    findOne: jest.fn(),
    save: jest.fn(async (value) => value),
    create: jest.fn((value) => value),
    ...overrides,
  };
  const service = new AiFormService(forms as never);
  return { service, forms };
}

const baseArgs = {
  userId: 7,
  conversationId: 3,
  assistantMessageId: 12,
};

const validSchema = {
  title: '新增学生',
  description: '填写学生基本信息',
  submitLabel: '确认新增',
  fields: [
    { name: 'name', label: '姓名', type: 'input', required: true },
    { name: 'gender', label: '性别', type: 'select', options: [{ label: '男', value: '男' }, { label: '女', value: '女' }] },
    { name: 'age', label: '年龄', type: 'number' },
  ],
};

describe('AiFormService', () => {
  describe('createForm', () => {
    it('校验通过的 schema 落库并保留完整字段', async () => {
      const { service, forms } = createService();
      const form = await service.createForm(baseArgs, validSchema);
      expect(forms.create).toHaveBeenCalledWith(
        expect.objectContaining({
          conversationId: 3,
          assistantMessageId: 12,
          title: '新增学生',
          submitLabel: '确认新增',
          status: 'pending',
        }),
      );
      expect(form.id).toBeTruthy();
      const fields = JSON.parse(form.fieldsJson) as unknown[];
      expect(fields).toHaveLength(3);
      expect(fields[1]).toEqual({
        name: 'gender',
        label: '性别',
        type: 'select',
        required: false,
        options: [{ label: '男', value: '男' }, { label: '女', value: '女' }],
      });
    });

    it('默认提交按钮文案为「提交」', async () => {
      const { service, forms } = createService();
      const { submitLabel, ...rest } = validSchema;
      await service.createForm(baseArgs, rest);
      expect(forms.create).toHaveBeenCalledWith(expect.objectContaining({ submitLabel: '提交' }));
    });

    it.each([
      ['标题缺失', { fields: validSchema.fields }, '表单标题'],
      ['字段为空', { ...validSchema, fields: [] }, '至少需要一个字段'],
      ['字段过多', { ...validSchema, fields: Array.from({ length: 13 }, (_, i) => ({ name: `f${i}`, label: `字段${i}`, type: 'input' })) }, '不能超过'],
      ['类型非法', { ...validSchema, fields: [{ name: 'x', label: 'X', type: 'checkbox' }] }, '类型不支持'],
      ['字段名非法', { ...validSchema, fields: [{ name: '姓 名', label: 'X', type: 'input' }] }, '只能包含'],
      ['字段名重复', { ...validSchema, fields: [{ name: 'x', label: 'A', type: 'input' }, { name: 'x', label: 'B', type: 'input' }] }, '字段名重复'],
      ['select 缺选项', { ...validSchema, fields: [{ name: 's', label: 'S', type: 'select' }] }, '选项数量'],
      ['未知字段', { ...validSchema, extra: 1 }, '未知字段'],
    ])('非法 schema 被拒绝：%s', async (_name, schema, messagePart) => {
      const { service } = createService();
      await expect(service.createForm(baseArgs, schema)).rejects.toBeInstanceOf(BadRequestException);
      await expect(service.createForm(baseArgs, schema)).rejects.toThrow(messagePart);
    });
  });

  describe('findOwnedPending', () => {
    it('只返回本人 pending 表单', async () => {
      const form = { id: 'form-1', userId: 7, status: 'pending' };
      const { service, forms } = createService({ findOne: jest.fn().mockResolvedValue(form) });
      await expect(service.findOwnedPending('form-1', 7)).resolves.toBe(form);
      expect(forms.findOne).toHaveBeenCalledWith({ where: { id: 'form-1', userId: 7, status: 'pending' } });
    });

    it('已提交或不存在时抛 NotFound', async () => {
      const { service } = createService({ findOne: jest.fn().mockResolvedValue(null) });
      await expect(service.findOwnedPending('form-1', 7)).rejects.toBeInstanceOf(NotFoundException);
    });
  });

  describe('validateValues', () => {
    const form = {
      fieldsJson: JSON.stringify(validSchema.fields),
    } as never;

    it('通过合法值并丢弃空的可选字段', () => {
      const { service } = createService();
      const values = service.validateValues(form, { name: '张三', age: 18 });
      expect(values).toEqual({ name: '张三', age: 18 });
    });

    it.each([
      ['必填缺失', { age: 18 }, '「姓名」为必填项'],
      ['未知字段', { name: '张三', hacker: 1 }, '未知字段'],
      ['数字类型错误', { name: '张三', age: '18' }, '必须是数字'],
      ['日期格式错误', { name: '张三', birthday: '18-01-2026' }, 'YYYY-MM-DD'],
      ['选项越界', { name: '张三', gender: '未知' }, '选项无效'],
    ])('非法值被拒绝：%s', async (_name, values, messagePart) => {
      const { service } = createService();
      const formWithDate = { fieldsJson: JSON.stringify([
        ...validSchema.fields,
        { name: 'birthday', label: '生日', type: 'date' },
      ]) } as never;
      await expect(() => service.validateValues(formWithDate, values)).toThrow(messagePart);
    });

    it('非对象提交被拒绝', () => {
      const { service } = createService();
      expect(() => service.validateValues(form, 'hacker')).toThrow(BadRequestException);
      expect(() => service.validateValues(form, ['hacker'])).toThrow(BadRequestException);
    });
  });

  describe('serialize', () => {
    it('回传前端所需结构', () => {
      const { service } = createService();
      const serialized = service.serialize({
        id: 'form-1',
        title: '新增学生',
        description: null,
        submitLabel: '提交',
        fieldsJson: JSON.stringify(validSchema.fields),
        status: 'submitted',
      } as never);
      expect(serialized).toEqual({
        id: 'form-1',
        title: '新增学生',
        description: null,
        submitLabel: '提交',
        fields: validSchema.fields,
        status: 'submitted',
      });
    });
  });

});
