import type { FilePermissionPolicy } from './filePermissions';

import { reactive } from 'vue';

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

import {
  filePermissionDesignerContext,
  isEditableFilePermissionPolicy,
  retainFilePermissions,
  validateDesignerFilePermissions,
  validateFilePermissionPolicy,
} from './filePermissions';

const fields = { form: new Set(['files', 'rows-files']) };
const nodes = new Set(['node']);
function policy(): FilePermissionPolicy {
  return {
    version: 1,
    rules: [
      {
        id: 'r1',
        formId: 'form',
        field: 'rows-files',
        subject: 'PARTICIPANTS',
        trigger: 'ENTERED',
        triggerNodeId: 'node',
        onReturn: 'KEEP',
        actions: { VIEW: 'ALLOW', DOWNLOAD: 'DENY' },
      },
    ],
  };
}

describe('workflow file permission configuration', () => {
  it('keeps same-form reactive upload configuration without sharing mutable state', () => {
    const previous = reactive({ jnpfKey: 'uploadFile', filePermissions: { DOWNLOAD: 'DENY' } });
    const next = retainFilePermissions(previous, { __config__: { jnpfKey: 'uploadFile' } }, true);
    expect(next.filePermissions).toEqual({ DOWNLOAD: 'DENY' });
    next.filePermissions!.DOWNLOAD = 'ALLOW';
    expect(previous.filePermissions.DOWNLOAD).toBe('DENY');
  });
  it('does not carry permissions across forms or component types', () => {
    const previous = { jnpfKey: 'uploadFile', filePermissions: { DOWNLOAD: 'DENY' } };
    expect(retainFilePermissions(previous, { __config__: { jnpfKey: 'uploadFile' } }, false)).toEqual({});
    expect(retainFilePermissions(previous, { __config__: { jnpfKey: 'input' } }, true)).toEqual({});
    expect(retainFilePermissions({ ...previous, jnpfKey: 'input' }, { __config__: { jnpfKey: 'uploadFile' } }, true)).toEqual({});
  });
  it('retains malformed configuration for validation instead of dropping it', () => {
    expect(retainFilePermissions({ jnpfKey: 'uploadFile', filePermissions: null }, { __config__: { jnpfKey: 'uploadFile' } }, true)).toEqual({
      filePermissions: null,
    });
  });
  it('does not add configuration to legacy nodes', () => {
    expect(retainFilePermissions(undefined, { __config__: { jnpfKey: 'uploadFile' } }, true)).toEqual({});
  });
  it('round-trips persistent child-field rules through JSON', () => {
    expect(validateFilePermissionPolicy(structuredClone(policy()), fields, nodes)).toEqual([]);
  });
  it('rejects deleted fields and nodes', () => {
    expect(validateFilePermissionPolicy(policy(), { form: new Set(['files']) }, nodes)).toHaveLength(1);
    expect(validateFilePermissionPolicy(policy(), fields, new Set())).toHaveLength(1);
  });
  it('requires explicit behavior after return', () => {
    const value = policy();
    delete value.rules[0]!.onReturn;
    expect(validateFilePermissionPolicy(value, fields, nodes)).toHaveLength(1);
  });
  it('rejects duplicate rules and malformed actions', () => {
    const value = policy();
    value.rules.push({ ...value.rules[0]! });
    expect(validateFilePermissionPolicy(value, fields, nodes)).toHaveLength(1);
    (value.rules[0]!.actions as any).DOWNLOAD = true;
    expect(validateFilePermissionPolicy(value, fields, nodes).length).toBeGreaterThan(1);
  });
  it('validates subject nodes and version without silently resetting permissions', () => {
    const value = policy();
    value.rules[0]!.subject = 'NODE_PARTICIPANTS';
    expect(validateFilePermissionPolicy(value, fields, nodes)).toHaveLength(1);
    value.rules[0]!.subjectNodeId = 'node';
    expect(validateFilePermissionPolicy(value, fields, nodes)).toEqual([]);
    expect(validateFilePermissionPolicy({ ...value, version: 2 } as any, fields, nodes)).toHaveLength(1);
  });
  it('accepts the mutually exclusive non-current-handler subject without a subject node', () => {
    const value = policy();
    value.rules[0]!.subject = 'NON_CURRENT_HANDLERS';
    expect(validateFilePermissionPolicy(value, fields, nodes)).toEqual([]);
  });
});

describe('designer file permission boundary', () => {
  const live = new Set(['gateway', 'node', 'start']);
  function data(): any {
    return {
      global: {
        allFormMap: {
          form_form: [
            { id: 'files', fullName: '主文件', __config__: { jnpfKey: 'uploadFile' } },
            { id: 'rows-files', fullName: '记录-文件', __config__: { jnpfKey: 'uploadFile' } },
            { id: 'name', __config__: { jnpfKey: 'input' } },
          ],
          form_foreign: [{ id: 'files', __config__: { jnpfKey: 'uploadFile' } }],
        },
      },
      start: { type: 'start', formId: 'form', formName: '审核表单' },
      node: { type: 'approver', nodeName: '确认' },
      gateway: { type: 'gateway', formId: 'foreign' },
      deleted: { type: 'approver', formId: 'foreign' },
    };
  }
  it('offers native main/child upload fields only from live human node forms', () => {
    const context = filePermissionDesignerContext(data(), live);
    expect(context.attachments.map((item) => [item.formId, item.field])).toEqual([
      ['form', 'files'],
      ['form', 'rows-files'],
    ]);
    expect(context.nodeIds).toEqual(new Set(['node', 'start']));
    expect(context.attachments[1]!.label).toBe('审核表单 / 记录-文件');
  });
  it('keeps unconfigured legacy flows of every type unchanged', () => {
    const value = data();
    const original = JSON.stringify(value);
    for (const type of [0, 1, 2, 3]) expect(validateDesignerFilePermissions(value, live, type)).toEqual([]);
    expect(JSON.stringify(value)).toBe(original);
  });
  it('validates native serialized config and blocks stale fields after a form switch', () => {
    const value = data();
    value.global.filePermissionPolicy = policy();
    expect(validateDesignerFilePermissions(structuredClone(value), live, 0)).toEqual([]);
    value.start.formId = 'foreign';
    expect(validateDesignerFilePermissions(value, live, 0)).toContain('附件字段不存在：r1');
  });
  it('rejects duplicate IDs across nodes but ignores removed nodes', () => {
    const value = data();
    value.global.filePermissionPolicy = policy();
    value.deleted.filePermissionPolicy = policy();
    expect(validateDesignerFilePermissions(value, live, 1)).toEqual([]);
    value.node.filePermissionPolicy = policy();
    expect(validateDesignerFilePermissions(value, live, 1)).toContain('文件规则 ID 重复：r1');
  });
  it('blocks unsupported flow types and reserved rule IDs while accepting return release', () => {
    const value = data();
    value.global.filePermissionPolicy = policy();
    expect(validateDesignerFilePermissions(value, live, 3)).toContain('当前流程类型暂不支持文件权限规则');
    value.global.filePermissionPolicy.rules[0].onReturn = 'RELEASE';
    value.global.filePermissionPolicy.rules[0].id = '@field/node/files';
    expect(validateDesignerFilePermissions(value, live, 0)).toEqual(['文件规则使用了保留标识']);
  });
  it('rejects malformed data without mutating or throwing', () => {
    for (const broken of [
      { version: 2, rules: [] },
      { version: 1, rules: [null] },
      { version: 1, rules: [{ id: 'r1', actions: null }] },
    ]) {
      const value = data();
      value.global.filePermissionPolicy = broken;
      const before = JSON.stringify(value);
      expect(isEditableFilePermissionPolicy(broken)).toBe(false);
      expect(validateDesignerFilePermissions(value, live, 0).length).toBeGreaterThan(0);
      expect(JSON.stringify(value)).toBe(before);
    }
  });
});
