import path from 'path';
import fs from 'fs-extra';
import { expect } from 'chai';
import { Auth0 } from '../src/tools';

import { cleanThenMkdir, testDataDir, mockMgmtClient } from './utils';
import {
  clearClientArrays,
  convertClientIdToName,
  dumpJSON,
  existsMustBeDir,
  formatResults,
  getFiles,
  isDirectory,
  isFile,
  loadJSON,
  mapClientID2NameSorted,
  recordsSorter,
  sanitize,
  stripIdentifiers,
  toConfigFn,
  encodeCertStringToBase64,
} from '../src/utils';

const mockConfigFn = () => {};

describe('#utils', function () {
  it('should check if directory exist', () => {
    const dirExist = path.join(testDataDir, 'utils', 'isdir');
    const dirNotExist = path.join(testDataDir, 'utils', 'notexist');
    cleanThenMkdir(dirExist);
    expect(isDirectory(dirExist)).is.equal(true);
    expect(isDirectory(dirNotExist)).is.equal(false);
  });

  it('should check if file exist', () => {
    const dir = path.join(testDataDir, 'utils', 'isfile');
    cleanThenMkdir(dir);

    const fileNotExist = path.join(dir, 'notexist');
    const fileExist = path.join(dir, 'file');
    fs.writeFileSync(fileExist, 'junk');

    expect(isFile(fileExist)).is.equal(true);
    expect(isFile(fileNotExist)).is.equal(false);
  });

  it('should get files', () => {
    const dir = path.join(testDataDir, 'utils', 'getfiles');
    cleanThenMkdir(dir);
    fs.writeFileSync(path.join(dir, 'test1.json'), 'junk');
    fs.writeFileSync(path.join(dir, 'test2.json'), 'junk');
    fs.writeFileSync(path.join(dir, 'test1.html'), 'junk');
    fs.writeFileSync(path.join(dir, 'test2.html'), 'junk');
    fs.writeFileSync(path.join(dir, 'other.file'), 'junk');

    expect(getFiles(dir, ['.json', '.html'])).to.deep.equal([
      path.join(dir, 'test1.html'),
      path.join(dir, 'test1.json'),
      path.join(dir, 'test2.html'),
      path.join(dir, 'test2.json'),
    ]);
  });

  it('should load json and replace keywords', () => {
    const dir = path.join(testDataDir, 'utils', 'json');
    cleanThenMkdir(dir);
    const file = path.join(dir, 'test1.json');
    fs.writeFileSync(file, '{"test": "123", "env1": @@env1@@, "env2": "##env2##"}');
    expect(loadJSON(file, { mappings: { env1: 'test1', env2: 'test2' } })).to.deep.equal({
      env1: 'test1',
      env2: 'test2',
      test: '123',
    });
  });

  it('exist must be dir', () => {
    const dirExist = path.join(testDataDir, 'utils', 'existmustbedir');
    const dirNotExist = path.join(testDataDir, 'utils', 'notexist');
    cleanThenMkdir(dirExist);
    expect(existsMustBeDir(dirExist)).is.equal(true);
    expect(existsMustBeDir(dirNotExist)).is.equal(false);
  });

  it('should return config fn', () => {
    const config = toConfigFn({ test: 'data' });
    expect(config('test')).to.equal('data');
  });

  it('should sanitized str for path/file', () => {
    expect(sanitize('test/some*/crazy//[path]')).to.equal('test-some--crazy--[path]');
  });

  it('should strip identifiers', () => {
    const assets = {
      clients: [{ name: 'some client', client_id: 'test' }],
      rulesConfigs: [{ key: 'test', value: 'test' }],
    };

    const auth0 = new Auth0(mockMgmtClient(), {}, mockConfigFn);

    expect(stripIdentifiers(auth0, assets)).to.deep.equal({
      clients: [
        {
          name: 'some client',
        },
      ],
      rulesConfigs: [
        {
          key: 'test',
          value: 'test',
        },
      ],
    });
  });

  it('should format object', () => {
    const result = formatResults({
      b: 'Bravo',
      id: 'Id',
      d: 'Delta',
      identifier: 'Identifier',
      c: 'Charlie',
      name: 'Name',
      a: 'Alpha',
    });
    expect(Object.keys(result)).to.deep.equal(['name', 'identifier', 'id', 'a', 'b', 'c', 'd']);
    expect(result).to.deep.equal({
      name: 'Name',
      identifier: 'Identifier',
      id: 'Id',
      a: 'Alpha',
      b: 'Bravo',
      c: 'Charlie',
      d: 'Delta',
    });
  });

  it('should not format result for string', () => {
    const result = formatResults('abc');
    expect(result).to.deep.equal('abc');
  });

  it('should sort records by name or template', () => {
    const name = [
      { name: 'b', id: 0 },
      { name: 'a', id: 1 },
      { name: 'c', id: 2 },
    ];
    const template = [
      { template: 'b', id: 0 },
      { template: 'a', id: 1 },
      { template: 'c', id: 2 },
    ];

    expect(name.sort(recordsSorter)).to.deep.equal([
      { name: 'a', id: 1 },
      { name: 'b', id: 0 },
      { name: 'c', id: 2 },
    ]);
    expect(template.sort(recordsSorter)).to.deep.equal([
      { template: 'a', id: 1 },
      { template: 'b', id: 0 },
      { template: 'c', id: 2 },
    ]);
  });

  it('should clear clients arrays', () => {
    const client = {
      name: 'Default App',
      callbacks: null,
      allowed_clients: null,
      allowed_logout_urls: null,
      is_first_party: true,
      oidc_conformant: false,
      allowed_origins: null,
    };

    expect(clearClientArrays(client)).to.deep.equal({
      name: 'Default App',
      callbacks: [],
      allowed_clients: [],
      allowed_logout_urls: [],
      is_first_party: true,
      oidc_conformant: false,
      allowed_origins: [],
    });
  });

  it('should not touch correct client arrays', () => {
    const client = {
      name: 'Default App',
      callbacks: ['callback'],
      allowed_clients: [],
      allowed_logout_urls: ['url', 'url'],
      is_first_party: true,
      oidc_conformant: false,
      allowed_origins: ['origin'],
    };

    expect(clearClientArrays(client)).to.deep.equal(client);
  });

  describe('dumpJSON', () => {
    const dir = path.join(testDataDir, 'utils', 'json');
    cleanThenMkdir(dir);
    const file = path.join(dir, 'test1.json');
    const testObject = {
      env1: 'test1',
      env2: 'test2',
      test: '123',
    };
    dumpJSON(file, testObject);
    const testFileContents = fs.readFileSync(file, { encoding: 'utf8' });

    it('should dump JSON with the contents of passed object', () => {
      expect(JSON.parse(testFileContents)).deep.equal(testObject);
    });
    it('should dump json with trailing newline', () => {
      expect(testFileContents).match(/\n$/g);
    });
    it('should throw an error if a path is not writable', () => {
      expect(() => {
        dumpJSON('http://notavalidfilepath', testObject);
      }).throws(/Error writing JSON.*/);
    });
  });

  describe('Client ID to Name conversions', () => {
    const knownClients = [
      {
        client_id: 'client_id_B',
        name: 'client_B',
      },
      {
        client_id: 'client_id_A',
        name: 'client_A',
      },
    ];

    it('should return client id if not found', () => {
      expect(convertClientIdToName('not_found_id', knownClients)).equal('not_found_id');
    });

    it('should return client id if known clients are undefined or empty or an object', () => {
      expect(convertClientIdToName('not_found_id', undefined)).equal('not_found_id');
      expect(convertClientIdToName('not_found_id', null)).equal('not_found_id');
      expect(convertClientIdToName('not_found_id', [])).equal('not_found_id');
      expect(convertClientIdToName('not_found_id', {})).equal('not_found_id');
    });

    it('should return client name if found', () => {
      expect(convertClientIdToName('client_id_B', knownClients)).equal('client_B');
      expect(convertClientIdToName('client_id_A', knownClients)).equal('client_A');
    });

    it('should return sorted list', () => {
      expect(
        mapClientID2NameSorted(['client_id_B', 'client_id_A', 'not_found_id'], knownClients)
      ).deep.equal(['client_A', 'client_B', 'not_found_id']);
    });

    it('should return sorted list even knownClient are invalid', () => {
      expect(
        mapClientID2NameSorted(['client_id_B', 'client_id_A', 'not_found_id'], null)
      ).deep.equal(['client_id_A', 'client_id_B', 'not_found_id']);
    });

    it('should return empty list upon invalid input', () => {
      expect(mapClientID2NameSorted(null, null)).deep.equal([]);
    });
  });

  describe('encodeCertStringToBase64', () => {
    it('should encode certificate string to Base64', () => {
      const cert =
        '-----BEGIN CERTIFICATE-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7\n-----END CERTIFICATE-----';
      const expectedBase64 = Buffer.from(cert).toString('base64');
      expect(encodeCertStringToBase64(cert)).to.equal(expectedBase64);
    });

    it('should return the original string if it does not start with "-----BEGIN CERTIFICATE-----"', () => {
      const nonCertString = 'This is not a certificate';
      expect(encodeCertStringToBase64(nonCertString)).to.equal(nonCertString);
    });

    it('should return the original string if it is null or undefined', () => {
      expect(encodeCertStringToBase64(null)).to.equal(null);
      expect(encodeCertStringToBase64(undefined)).to.equal(undefined);
    });
  });
});
