const R = require('ramda');
const { getDomainService, diffRecords } = require('../utils/domain-service');
const { DOMAIN_DOMAIN } = require('../utils/constants');

const getCpanel = ({ zone, addZone, removeZone, redir, addRedir, removeRedir, addEmail, removeEmail } = {}) => ({
  zone: {
    fetch: (_) => zone(),
    add: (rec) => addZone(rec),
    remove: (rec) => removeZone(rec),
  },
  redirection: {
    fetch: (_) => redir(),
    add: (rec) => addRedir(rec),
    remove: (rec) => removeRedir(rec),
  },
  email: {
    add: (rec) => addEmail(rec),
    remove: (rec) => removeEmail(rec),
  },
});

describe('diffRecords', () => {
  it('should show added record', () => {
    const oldRecords = [
      { name: 'xx', type: 'CNAME', address: 'fck.com.' },
      { name: 'xa', type: 'A', address: '111.1.1212.1' },
    ];
    const newRecords = [
      { name: 'xx', type: 'CNAME', address: 'fck.com.' },
      { name: 'xa', type: 'A', address: '111.1.1212.1' },
      { name: 'boo', type: 'CNAME', address: 'x.com' },
    ];

    const result = diffRecords(oldRecords, newRecords);
    expect(result).toEqual({
      remove: [],
      add: [
        { name: 'boo', type: 'CNAME', address: 'x.com' },
      ],
    });
  });

  it('should show edited records', () => {
    const oldRecords = [
      { name: 'xx', type: 'CNAME', address: 'fck.com.' },
      { name: 'xa', type: 'A', address: '111.1.1212.1' },
    ];
    const newRecords = [
      { name: 'xx', type: 'CNAME', address: 'fck.com.' },
      { name: 'xa', type: 'A', address: '69.69.69.69' },
    ];

    const result = diffRecords(oldRecords, newRecords);
    expect(result).toEqual({
      remove: [
        { name: 'xa', type: 'A', address: '111.1.1212.1' },
      ],
      add: [
        { name: 'xa', type: 'A', address: '69.69.69.69' },
      ],
    });
  });

  it('should show added records with the same name and record type', () => {
    const oldRecords = [
      { name: 'xx', type: 'CNAME', address: 'fck.com.' },
      { name: 'xa', type: 'A', address: '69.69.69.69' },
    ];
    const newRecords = [
      { name: 'xx', type: 'CNAME', address: 'fck.com.' },
      { name: 'xa', type: 'A', address: '69.69.69.69' },
      { name: 'xa', type: 'A', address: '69.69.4.20' },
    ];

    const result = diffRecords(oldRecords, newRecords);
    expect(result).toEqual({
      remove: [],
      add: [
        { name: 'xa', type: 'A', address: '69.69.4.20' },
      ],
    });
  });

  it('should diff complex changes', () => {
    const oldRecords = [
      { name: 'a', type: 'CNAME', address: 'fck.com.' },
      { name: 'b', type: 'A', address: '69.69.69.69' },
      { name: '111', type: 'CNAME', address: 'x' },
      { name: 'd', type: 'A', address: '69.69.4.20' },
    ];
    const newRecords = [
      { name: '111', type: 'CNAME', address: 'x' },
      { name: 'd', type: 'CNAME', address: 'duck.com' },
      { name: 'a', type: 'CNAME', address: 'og.com' },
      { name: 'b', type: 'A', address: '69.69.69.69' },
      { name: 'b', type: 'A', address: '69.69.4.20' },
      { name: 'c', type: 'CNAME', address: 'ccc.cc' },
    ];

    const result = diffRecords(oldRecords, newRecords);
    expect(result).toEqual({
      remove: [
        { name: 'a', type: 'CNAME', address: 'fck.com.' },
        { name: 'd', type: 'A', address: '69.69.4.20' },
      ],
      add: [
        { name: 'd', type: 'CNAME', address: 'duck.com' },
        { name: 'a', type: 'CNAME', address: 'og.com' },
        { name: 'b', type: 'A', address: '69.69.4.20' },
        { name: 'c', type: 'CNAME', address: 'ccc.cc' },
      ],
    });
  });
});

describe('Domain service', () => {
  const addZone = jest.fn(async () => ({}));
  const removeZone = jest.fn(async () => ({}));
  const addRedir = jest.fn(async () => ({}));
  const removeRedir = jest.fn(async () => ({}));
  const addEmail = jest.fn(async () => ({}));
  const removeEmail = jest.fn(async () => ({}));

  const mockDS = ({ zones, redirections }) => getDomainService({
    cpanel: getCpanel({
      zone: async () => zones,
      redir: async () => redirections,
      addZone,
      addEmail,
      addRedir,
      removeZone,
      removeRedir,
      removeEmail,
    })
  });

  const getRecordCalls = recfn => recfn.mock.calls
    .map(R.head)
    .map(R.pick(['name', 'type', 'address', 'redirect', 'domain', 'line', 'priority', 'exchanger']));

  beforeEach(() => {
    addZone.mockClear();
    removeZone.mockClear();
    addRedir.mockClear();
    removeRedir.mockClear();
    addEmail.mockClear();
    removeEmail.mockClear();
  });

  describe('getHosts', () => {
    it('should resolve with a list of hosts', async () => {
      const zones = [
        { name: 'xx', type: 'CNAME', address: 'fck.com.' },
        { name: 'xx', type: 'A', address: '111.1.1212.1' },
      ];
      const redirections = [];
      const zone = async () => zones;
      const redir = async () => redirections;
      const mockDomainService = getDomainService({ cpanel: getCpanel({ zone, redir }) });
      const list = await mockDomainService.getHosts();

      expect(list).toEqual([
        { name: 'xx', type: 'CNAME', address: 'fck.com' },
        { name: 'xx', type: 'A', address: '111.1.1212.1' },
      ]);
    });

    it('should resolve with a redirections', async () => {
      const zones = [
        { line: '111', name: 'xx', type: 'CNAME', address: 'fck.com.' },
        { line: '112', name: 'xx', type: 'A', address: '111.1.1212.1' },
      ];
      const redirections = [
        { domain: 'foo.booboo.xyz', destination: 'https://google.com' },
        { domain: 'foo1.booboo.xyz', destination: 'https://duck.com' },
      ];
      const zone = async () => zones;
      const redir = async () => redirections;
      const mockDomainService = getDomainService({ cpanel: getCpanel({ zone, redir }) });
      const list = await mockDomainService.getHosts();

      expect(list).toEqual([
        { id: '111', name: 'xx', type: 'CNAME', address: 'fck.com' },
        { id: '112', name: 'xx', type: 'A', address: '111.1.1212.1' },
        { id: `foo.${DOMAIN_DOMAIN}`, name: 'foo', type: 'URL', address: 'https://google.com' },
        { id: `foo1.${DOMAIN_DOMAIN}`, name: 'foo1', type: 'URL', address: 'https://duck.com' },
      ]);
    });
  });

  describe('updateHosts', () => {
    it('should append new hosts with existing ones and set it', async () => {
      const zones = [
        { line: 1, name: 'a', type: 'CNAME', address: 'boo' },
        { line: 2, name: 'b', type: 'CNAME', address: 'goo' },
      ];
      const redirections = [];

      const mockDomainService = mockDS({ zones, redirections });
      await mockDomainService.updateHosts([
        { name: 'a', type: 'CNAME', address: 'boo' },
        { name: 'b', type: 'CNAME', address: 'goo' },
        { name: 'c', type: 'A', address: '12.131321.213' },
        { name: 'c', type: 'MX', address: 'foobar.com', priority: 2 },
      ]);

      expect(addZone).toHaveBeenCalledTimes(1);
      expect(getRecordCalls(addZone)).toEqual([
        { name: 'c', type: 'A', address: '12.131321.213' },
      ]);

      expect(addEmail).toHaveBeenCalledTimes(1);
      expect(getRecordCalls(addEmail)).toEqual([
        { domain: 'c.is-a.dev', exchanger: 'foobar.com', priority: 2 },
      ]);

      expect(removeZone).toHaveBeenCalledTimes(0);
      expect(removeEmail).toHaveBeenCalledTimes(0);
    });

    it('should update matching host and set it', async () => {
      const zones = [
        { line: 1, name: 'a', type: 'CNAME', address: 'boo' },
        { line: 2, name: 'b', type: 'CNAME', address: 'goo' },
      ];
      const redirections = [];

      const mockDomainService = mockDS({ zones, redirections });
      await mockDomainService.updateHosts([
        { name: 'a', type: 'CNAME', address: 'boo' },
        { name: 'b', type: 'CNAME', address: 'googoogaga' },
      ]);

      expect(addZone).toHaveBeenCalledTimes(1);
      expect(getRecordCalls(addZone)).toEqual([
        { name: 'b', type: 'CNAME', address: 'googoogaga' },
      ]);
      expect(removeZone).toHaveBeenCalledTimes(1);
      expect(getRecordCalls(removeZone)).toEqual([
        { line: 2 },
      ]);
    });

    it('should update matching host and set it', async () => {
      const zones = [
        { line: 1, name: 'a', type: 'CNAME', address: 'boo' },
        { line: 2, name: 'b', type: 'CNAME', address: 'goo' },
        { line: 3, name: 'b', type: 'CNAME', address: 'xaa' },
      ];
      const redirections = [];

      const mockDomainService = mockDS({ zones, redirections });
      await mockDomainService.updateHosts([
        { name: 'a', type: 'CNAME', address: 'boo' },
        { name: 'b', type: 'CNAME', address: 'googoogaga' },
        { name: 'b', type: 'CNAME', address: 'farboo' },
      ]);

      expect(addZone).toHaveBeenCalledTimes(2);
      expect(getRecordCalls(addZone)).toEqual([
        { name: 'b', type: 'CNAME', address: 'googoogaga' },
        { name: 'b', type: 'CNAME', address: 'farboo' },
      ]);
      expect(removeZone).toHaveBeenCalledTimes(2);
      expect(getRecordCalls(removeZone)).toEqual([
        { line: 3 },
        { line: 2 },
      ]);
    });

    it('should workout this complex example', async () => {
      const zones = [
        { line: 1, name: 'a', type: 'CNAME', address: 'world' },
        { line: 2, name: 'b', type: 'A', address: '1' },
        { line: 3, name: 'b', type: 'A', address: '2' },
        { line: 4, name: 'c', type: 'CNAME', address: 'hello.com' },
        { line: 5, name: 'c', type: 'MX', address: 'mx1.hello.com', priority: 20 },
        { line: 6, name: 'c', type: 'MX', address: 'mx2.hello.com', priority: 21 },
        { line: 7, name: 'b', type: 'MX', address: 'foo.bar', priority: 20 },
        { line: 101, name: 'x', type: 'A', address: '1' },
        { line: 99, name: 'y', type: 'A', address: '2' },
        { line: 100, name: 'z', type: 'A', address: '3' },
      ];
      const redirections = [
        { domain: `b.${DOMAIN_DOMAIN}`, destination: 'https://foobar.com' },
        { domain: `c.${DOMAIN_DOMAIN}`, destination: 'https://goobar.com' },
        { domain: `x.${DOMAIN_DOMAIN}`, destination: 'https://example.com' },
      ];

      const mockDomainService = mockDS({ zones, redirections });
      await mockDomainService.updateHosts([
        { name: 'a', type: 'CNAME', address: 'boo' },
        { name: 'b', type: 'A', address: '1' },
        { name: 'b', type: 'A', address: '2' },
        { name: 'b', type: 'A', address: '3' },
        { name: 'b', type: 'URL', address: 'https://wowow.com' },
        { name: 'c', type: 'CNAME', address: 'hello.com' },
        { name: 'c', type: 'URL', address: 'https://goobar.com' },
        { name: 'd', type: 'CNAME', address: 'helo.com' },
        { name: 'd', type: 'URL', address: 'https://hhh.com' },
        { name: 'x', type: 'URL', address: 'https://example69.com' },
        { name: 'c', type: 'MX', address: 'mx2.hello.com', priority: 21 },
        { name: 'a', type: 'MX', address: 'example.com', priority: 20 },
      ]);

      expect(addZone).toHaveBeenCalledTimes(3);
      expect(getRecordCalls(addZone)).toEqual([
        { name: 'a', type: 'CNAME', address: 'boo' },
        { name: 'b', type: 'A', address: '3' },
        { name: 'd', type: 'CNAME', address: 'helo.com' },
      ]);
      expect(removeZone).toHaveBeenCalledTimes(4);
      expect(getRecordCalls(removeZone)).toEqual([
        { line: 101 },
        { line: 100 },
        { line: 99 },
        { line: 1 },
      ]);

      expect(addEmail).toHaveBeenCalledTimes(1);
      expect(getRecordCalls(addEmail)).toEqual([
        { domain: 'a.is-a.dev', exchanger: 'example.com', priority: 20 },
      ]);
      expect(removeEmail).toHaveBeenCalledTimes(2);
      expect(getRecordCalls(removeEmail)).toEqual([
        { domain: 'b.is-a.dev', exchanger: 'foo.bar', priority: 20 },
        { domain: 'c.is-a.dev', exchanger: 'mx1.hello.com', priority: 20 },
      ]);

      expect(addRedir).toHaveBeenCalledTimes(3);
      expect(getRecordCalls(addRedir)).toEqual([
        { domain: `b.${DOMAIN_DOMAIN}`, type: 'permanent', redirect: 'https://wowow.com' },
        { domain: `d.${DOMAIN_DOMAIN}`, type: 'permanent', redirect: 'https://hhh.com' },
        { domain: `x.${DOMAIN_DOMAIN}`, type: 'permanent', redirect: 'https://example69.com' },
      ]);
      expect(removeRedir).toHaveBeenCalledTimes(2);
      expect(getRecordCalls(removeRedir)).toEqual([
        { domain: `b.${DOMAIN_DOMAIN}` },
        { domain: `x.${DOMAIN_DOMAIN}` },
      ]);
    });
  });
});

