import { DebugElement } from '@angular/core';
import { ComponentFixture, discardPeriodicTasks, fakeAsync } from '@angular/core/testing';
import { createTestContext } from '@ohayo/testing';
import { deepCopy } from '@ohayo/util';
import { ObjectProperty } from '../src/model/object.property';
import { SFSchema } from '../src/schema/index';
import { SFUISchema, SFUISchemaItem } from '../src/schema/ui';
import { configureSFTestSuite, SFPage, TestFormComponent } from './base.spec';

describe('form: schema', () => {
  let fixture: ComponentFixture<TestFormComponent>;
  let dl: DebugElement;
  let context: TestFormComponent;
  let page: SFPage;

  configureSFTestSuite();

  beforeEach(() => {
    ({ fixture, dl, context } = createTestContext(TestFormComponent));
    fixture.detectChanges();
    page = new SFPage(context.comp);
    page.prop(dl, context, fixture);
  });

  describe('[cover schema]', () => {
    beforeEach(() => spyOn(console, 'warn'));
    it('should be using select widget when not ui and enum exists', () => {
      page
        .newSchema({
          properties: {
            name: { type: 'string', enum: ['a'] },
          },
        })
        .checkUI('/name', 'widget', 'select');
    });
    it('should be using autocomplete widget when format equal email', () => {
      page
        .newSchema({
          properties: {
            name: { type: 'string', format: 'email' },
          },
        })
        .checkUI('/name', 'widget', 'autocomplete');
    });
    it('support ui property is a string', () => {
      page
        .newSchema({
          properties: {
            name: { type: 'string', ui: 'textarea' },
          },
        })
        .checkUI('/name', 'widget', 'textarea');
    });
    it('should be inherit all properties with * for ui schema', () => {
      const schema: SFSchema = {
        properties: {
          name1: { type: 'string' },
          name2: {
            type: 'array',
            items: {
              type: 'object',
              properties: {
                a: {
                  type: 'string',
                  ui: {
                    grid: { span: 12 },
                  },
                },
                b: { type: 'string' },
              },
              ui: { spanLabelFixed: 10 },
            },
          },
        },
      };
      const label = 10;
      const ui: SFUISchema = {
        '*': { spanLabel: label },
        $name2: {
          $items: {
            $a: { spanLabel: 9 },
          },
        },
      };
      page
        .newSchema(schema, ui)
        .checkUI('/name1', 'spanLabel', label)
        .add()
        .checkUI('/name2/0/a', 'spanLabel', null) // 当指定标签为固定宽度时无须指定 `spanLabel`，`spanControl` 会强制清理
        .checkUI('/name2/0/b', 'spanLabelFixed', 10);
    });
    it('should be fixed label width', () => {
      const schema: SFSchema = {
        properties: {
          name: { type: 'string' },
          protocol: {
            type: 'boolean',
            title: '同意本协议',
            description: '《用户协议》',
            ui: {
              widget: 'checkbox',
            },
            default: true,
          },
        },
        ui: { spanLabelFixed: 10, debug: true },
      };
      page.newSchema(schema).checkUI('/name', 'spanLabelFixed', 10).checkUI('/protocol', 'spanLabelFixed', 10);
    });
    it('support invalid format value', () => {
      page
        .newSchema({
          properties: {
            name: { type: 'string', format: 'email1' },
          },
        })
        .checkUI('/name', 'widget', 'string');
    });
    it('should be null spanLabel when not horizontal layout', () => {
      context.layout = 'inline';
      fixture.detectChanges();
      page.checkUI('/name', 'spanLabel', null);
    });
    it('should call refreshSchema changed schema', fakeAsync(() => {
      context.comp.refreshSchema(
        {
          properties: {
            user: {
              type: 'object',
              properties: {
                name: { type: 'string' },
                age: { type: 'number' },
              },
            },
          },
        },
        { '*': { spanLabelFixed: 100, spanControl: 10, offsetControl: 11 } },
      );
      page.checkUI('/user/name', 'spanLabelFixed', 100);
      page.checkUI('/user/name', 'spanControl', null); // 当指定标签为固定宽度时无须指定 `spanLabel`，`spanControl` 会强制清理
      page.checkUI('/user/name', 'offsetControl', 11);
      discardPeriodicTasks();
    }));
    it('support ui is null', () => {
      expect(() => {
        context.ui = null;
        fixture.detectChanges();
      }).not.toThrow();
    });
    describe('#array', () => {
      const arrUI: SFUISchemaItem = { spanLabel: 10, grid: { arraySpan: 12 } };
      const arrSchema: SFSchema = {
        properties: {
          name: {
            type: 'array',
            items: {
              type: 'object',
              properties: {
                a: { type: 'string' },
              },
            },
          },
        },
      };
      describe('[#via in json schema]', () => {
        it('should be has $items when is array', () => {
          const schema = deepCopy(arrSchema) as SFSchema;
          schema.properties!.name.ui = deepCopy(arrUI);
          page.newSchema(schema).checkUI('/name', 'grid.arraySpan', arrUI.grid!.arraySpan);
        });
      });
      describe('[#via ui property]', () => {
        it('should be has $items when is array', () => {
          const schema = deepCopy(arrSchema);
          const uiSchema: SFUISchema = {
            $name: {
              $items: {},
              ...deepCopy(arrUI),
            },
          };
          page.newSchema(schema, uiSchema).checkUI('/name', 'grid.arraySpan', arrUI.grid!.arraySpan);
        });
      });
    });
    describe('#optionalHelp', () => {
      it('should working when value is string', fakeAsync(() => {
        context.comp.refreshSchema({
          properties: {
            name: { type: 'string', ui: { optionalHelp: 'a' } },
          },
        });
        page.checkCount('.sf__optional [nz-tooltip]', 1);
        discardPeriodicTasks();
      }));
      it('should working when value is object', fakeAsync(() => {
        context.comp.refreshSchema({
          properties: {
            name: { type: 'string', ui: { optionalHelp: { text: 'b', placement: 'bottomRight' } } },
          },
        });
        page.checkCount('.sf__optional [nz-tooltip]', 1);
        discardPeriodicTasks();
      }));
      it('should be hide when not text value in object', fakeAsync(() => {
        context.comp.refreshSchema({
          properties: {
            name: { type: 'string', ui: { optionalHelp: { text: '', placement: 'bottomRight' } } },
          },
        });
        page.checkCount('.sf__optional [nz-tooltip]', 0);
        discardPeriodicTasks();
      }));
      it('should be inherit the root config', fakeAsync(() => {
        context.comp.refreshSchema(
          {
            properties: {
              name: { type: 'string', ui: { optionalHelp: { text: 'a' } } },
            },
          },
          { '*': { optionalHelp: { text: '', placement: 'bottomRight' } } },
        );
        const prop = page.getProperty('/name');
        expect((prop!.ui!.optionalHelp as any).placement!).toBe(`bottomRight`);
        discardPeriodicTasks();
      }));
    });
  });

  describe('[definitions]', () => {
    it('should be ref definitions', () => {
      page
        .newSchema({
          definitions: {
            large: {
              type: 'string',
              maxLength: 10,
            },
          },
          properties: {
            name: { $ref: '#/definitions/large' },
          },
        })
        .checkSchema('/name', 'maxLength', 10);
    });
    it('should be throw error when not fond defind', () => {
      expect(() => {
        page.newSchema({
          definitions: {},
          properties: {
            name: { $ref: '#/definitions/large' },
          },
        });
      }).toThrow();
    });
    it('should be throw error when is invalid $ref', () => {
      expect(() => {
        page.newSchema({
          definitions: {},
          properties: {
            name: { $ref: 'definitions/large' },
          },
        });
      }).toThrow();
    });
  });

  describe('[if]', () => {
    it('should be changed login type via if', () => {
      page
        .newSchema({
          properties: {
            login_type: {
              type: 'string',
              title: '登录方式',
              enum: [
                { label: '手机', value: 'mobile' },
                { label: '账密', value: 'account' },
              ],
              default: 'mobile',
              ui: {
                widget: 'radio',
                class: 'j-login_type',
              },
            },
            mobile: { type: 'string', ui: { class: 'j-mobile' } },
            code: { type: 'number', ui: { class: 'j-code' } },
            name: { type: 'string', ui: { class: 'j-name' } },
            pwd: {
              type: 'string',
              ui: {
                type: 'password',
                class: 'j-pwd',
              },
            },
          },
          required: ['login_type'],
          if: {
            properties: { login_type: { enum: ['mobile'] } },
          },
          then: {
            required: ['mobile', 'code'],
          },
          else: {
            required: ['name', 'pwd'],
          },
        })
        .checkCount('.j-mobile', 1)
        .checkCount('.j-name', 0);
      const labels = page.getEl('.j-login_type nz-radio-group').querySelectorAll('label');
      expect(labels.length).toBe(2);
      labels[1].click();
      page.checkCount('.j-mobile', 0).checkCount('.j-name', 1);
    });
    it('should be not else', () => {
      page
        .newSchema({
          properties: {
            login_type: {
              type: 'string',
              title: '登录方式',
              enum: [
                { label: '手机', value: 'mobile' },
                { label: '账密', value: 'account' },
              ],
              default: 'mobile',
              ui: {
                widget: 'radio',
                class: 'j-login_type',
              },
            },
            mobile: { type: 'string', ui: { class: 'j-mobile' } },
            code: { type: 'number', ui: { class: 'j-code' } },
            name: { type: 'string', ui: { class: 'j-name' } },
            pwd: {
              type: 'string',
              ui: {
                type: 'password',
                class: 'j-pwd',
              },
            },
          },
          required: ['login_type'],
          if: {
            properties: { login_type: { enum: ['mobile'] } },
          },
          then: {
            required: ['mobile', 'code'],
          },
        })
        .checkCount('.j-mobile', 1)
        .checkCount('.j-name', 1);
      const labels = page.getEl('.j-login_type nz-radio-group').querySelectorAll('label');
      expect(labels.length).toBe(2);
      labels[1].click();
      page.checkCount('.j-mobile', 0).checkCount('.j-name', 1);
    });
    it(`should be throw error when is not 'properties' for if`, () => {
      expect(() => {
        page.newSchema({
          properties: { name: { type: 'string' } },
          if: {},
          then: {},
        });
      }).toThrowError(`if: does not contain 'properties'`);
    });
    it(`should be throw error when invalid key for 'properties' in if`, () => {
      expect(() => {
        page.newSchema({
          properties: {
            login_type: {
              type: 'string',
              title: '登录方式',
              enum: [
                { label: '手机', value: 'mobile' },
                { label: '账密', value: 'account' },
              ],
              default: 'mobile',
              ui: {
                widget: 'radio',
                class: 'j-login_type',
              },
            },
            mobile: { type: 'string', ui: { class: 'j-mobile' } },
            code: { type: 'number', ui: { class: 'j-code' } },
            name: { type: 'string', ui: { class: 'j-name' } },
            pwd: {
              type: 'string',
              ui: {
                type: 'password',
                class: 'j-pwd',
              },
            },
          },
          required: ['login_type'],
          if: {
            properties: { login_type1: { enum: ['mobile'] } },
          },
          then: {
            required: ['mobile', 'code'],
          },
        });
      }).toThrow();
    });
  });

  describe('[order]', () => {
    function genKeys(): string {
      return JSON.stringify(Object.keys((context.comp.rootProperty as ObjectProperty).properties!));
    }

    function checkOrderKeys(arr: string[]): void {
      expect(genKeys()).toBe(JSON.stringify(arr));
    }

    it('should be adjust the order', () => {
      const order = ['pwd', 'name'];
      page.newSchema({
        properties: {
          name: { type: 'string' },
          pwd: { type: 'string' },
        },
        ui: {
          order,
        },
      });
      checkOrderKeys(order);
    });

    it('should be used *', () => {
      page.newSchema({
        properties: {
          a: { type: 'string' },
          b: { type: 'string' },
          c: { type: 'string' },
        },
        ui: {
          order: ['b', '*'],
        },
      });
      checkOrderKeys(['b', 'a', 'c']);
    });

    describe('should be throw error', () => {
      beforeEach(() => spyOn(console, 'error'));
      it('when has extraneous key', () => {
        expect(() => {
          page.newSchema({
            properties: {
              a: { type: 'string' },
              b: { type: 'string' },
            },
            ui: {
              order: ['c', 'a'],
            },
          });
        }).toThrow();
      });
      it('when does not contain all keys', () => {
        expect(() => {
          page.newSchema({
            properties: {
              a: { type: 'string' },
              b: { type: 'string' },
            },
            ui: {
              order: ['a'],
            },
          });
        }).toThrow();
      });
      it('when contains more than one wildcard item', () => {
        expect(() => {
          page.newSchema({
            properties: {
              a: { type: 'string' },
              b: { type: 'string' },
            },
            ui: {
              order: ['a', '*', '*', '*', '*'],
            },
          });
        }).toThrow();
      });
    });
  });

  describe('[$ref]', () => {
    it('should be required valid', () => {
      page
        .newSchema({
          definitions: {
            nameRef: {
              type: 'string',
              title: 'nameRef',
            },
          },
          properties: {
            name: {
              type: 'string',
              title: 'Name',
            },
            nameTwo: {
              $ref: '#/definitions/nameRef',
            },
          },
          required: ['name', 'nameTwo'],
        })
        .checkUI('/nameTwo', '_required', true);
    });
  });
});
