import {generateGravatarUrl, GENERIC_AVATAR } from '../../../../src/utils/user';
import { spliceURL } from '../../../../src/utils/string';
import {
  validateName,
  convertDistRemoteToLocalTarballUrls,
  parseReadme,
  addGravatarSupport,
  validatePackage,
  validateMetadata,
  combineBaseUrl,
  getVersion,
  normalizeDistTags,
  getWebProtocol,
  getVersionFromTarball,
  sortByName,
  formatAuthor,
  isHTTPProtocol,
} from '../../../../src/lib/utils';
import { DIST_TAGS, DEFAULT_USER } from '../../../../src/lib/constants';
import { logger, setup } from '../../../../src/lib/logger';
import { readFile } from '../../../functional/lib/test.utils';

const readmeFile = (fileName = 'markdown.md') =>
  readFile(`../../unit/partials/readme/${fileName}`);

setup([]);

describe('Utilities', () => {
  const buildURI = (host, version) =>
    `http://${host}/npm_test/-/npm_test-${version}.tgz`;
  const fakeHost = 'fake.com';
  const metadata: any = {
    name: 'npm_test',
    versions: {
      '1.0.0': {
        dist: {
          tarball: 'http://registry.org/npm_test/-/npm_test-1.0.0.tgz'
        }
      },
      '1.0.1': {
        dist: {
          tarball: 'http://registry.org/npm_test/-/npm_test-1.0.1.tgz'
        }
      }
    }
  };

  const cloneMetadata = (pkg = metadata) => Object.assign({}, pkg);

  describe('API utilities', () => {
    describe('Sort packages', () => {
      const packages = [
        {
          name: 'ghc'
        },
        {
          name: 'abc'
        },
        {
          name: 'zxy'
        }
      ];
      test('should order ascending', () => {
        expect(sortByName(packages)).toEqual([
          {
            name: 'abc'
          },
          {
            name: 'ghc'
          },
          {
            name: 'zxy'
          }
        ]);
      });

      test('should order descending', () => {
        expect(sortByName(packages, false)).toEqual([
          {
            name: 'zxy'
          },
          {
            name: 'ghc'
          },
          {
            name: 'abc'
          }
        ]);
      });
    });

    describe('getWebProtocol', () => {
      test('should handle undefined header', () => {
        expect(getWebProtocol(undefined, 'http')).toBe('http');
      });

      test('should handle emtpy string', () => {
        expect(getWebProtocol('', 'http')).toBe('http');
      });

      test('should have header priority over request protocol', () => {
        expect(getWebProtocol("https", 'http')).toBe('https');
      });

      test('should have handle empty protocol', () => {
        expect(getWebProtocol("https", '')).toBe('https');
      });

      describe('getWebProtocol and HAProxy variant', () => {
        // https://github.com/verdaccio/verdaccio/issues/695

        test('should handle http', () => {
          expect(getWebProtocol("http,http", 'https')).toBe('http');
        });

        test('should handle https', () => {
          expect(getWebProtocol("https,https", 'http')).toBe('https');
        });
      });
    });

    describe('convertDistRemoteToLocalTarballUrls', () => {
      test('should build a URI for dist tarball based on new domain', () => {
        const convertDist = convertDistRemoteToLocalTarballUrls(cloneMetadata(),
          {
            headers: {
              host: fakeHost
            },
            // @ts-ignore
            get: () => 'http',
            protocol: 'http'
          });
        expect(convertDist.versions['1.0.0'].dist.tarball).toEqual(buildURI(fakeHost, '1.0.0'));
        expect(convertDist.versions['1.0.1'].dist.tarball).toEqual(buildURI(fakeHost, '1.0.1'));
      });

      test('should return same URI whether host is missing', () => {
        const convertDist = convertDistRemoteToLocalTarballUrls(cloneMetadata(),
          {
            headers: {},
            // @ts-ignore
            get: () => 'http',
            protocol: 'http'
          });
        expect(convertDist.versions['1.0.0'].dist.tarball).toEqual(convertDist.versions['1.0.0'].dist.tarball);
      });
    });

    describe('normalizeDistTags', () => {
      test('should delete a invalid latest version', () => {
        const pkg = cloneMetadata();
        pkg[DIST_TAGS] = {
          latest: '20000'
        };

        normalizeDistTags(pkg)

        expect(Object.keys(pkg[DIST_TAGS])).toHaveLength(0);
      });

      test('should define last published version as latest', () => {
        const pkg = cloneMetadata();
        pkg[DIST_TAGS] = {};

        normalizeDistTags(pkg)

        expect(pkg[DIST_TAGS]).toEqual({latest: '1.0.1'});
      });

      test('should define last published version as latest with a custom dist-tag', () => {
        const pkg = cloneMetadata();
        pkg[DIST_TAGS] = {
          beta: '1.0.1'
        };

        normalizeDistTags(pkg);

        expect(pkg[DIST_TAGS]).toEqual({beta: '1.0.1', latest: '1.0.1'});
      });

      test('should convert any array of dist-tags to a plain string', () => {
        const pkg = cloneMetadata();
        pkg[DIST_TAGS] = {
          latest: ['1.0.1']
        };

        normalizeDistTags(pkg);

        expect(pkg[DIST_TAGS]).toEqual({latest: '1.0.1'});
      });
    });

    describe('getVersion', () => {
      test('should get the right version', () => {
        expect(getVersion(cloneMetadata(), '1.0.0')).toEqual(metadata.versions['1.0.0']);
        expect(getVersion(cloneMetadata(), 'v1.0.0')).toEqual(metadata.versions['1.0.0']);
      });

      test('should return nothing on get non existing version', () => {
        expect(getVersion(cloneMetadata(), '0')).toBeUndefined();
        expect(getVersion(cloneMetadata(), '2.0.0')).toBeUndefined();
        expect(getVersion(cloneMetadata(), 'v2.0.0')).toBeUndefined();
        expect(getVersion(cloneMetadata(), undefined)).toBeUndefined();
        expect(getVersion(cloneMetadata(), null)).toBeUndefined();
        expect(getVersion(cloneMetadata(), 2)).toBeUndefined();
      })
    });

    describe('combineBaseUrl', () => {
      test('should create a URI', () => {
        expect(combineBaseUrl("http", 'domain')).toEqual('http://domain');
      });

      test('should create a base url for registry', () => {
        expect(combineBaseUrl("http", 'domain', '')).toEqual('http://domain');
        expect(combineBaseUrl("http", 'domain', '/')).toEqual('http://domain');
        expect(combineBaseUrl("http", 'domain', '/prefix/')).toEqual('http://domain/prefix');
        expect(combineBaseUrl("http", 'domain', '/prefix/deep')).toEqual('http://domain/prefix/deep');
        expect(combineBaseUrl("http", 'domain', 'only-prefix')).toEqual('only-prefix');
      });

    });

    describe('validatePackage', () => {
      test('should validate package names', () => {
        expect(validatePackage("package-name")).toBeTruthy();
        expect(validatePackage("@scope/package-name")).toBeTruthy();
      });

      test('should fails on validate package names', () => {
        expect(validatePackage("package-name/test/fake")).toBeFalsy();
        expect(validatePackage("@/package-name")).toBeFalsy();
        expect(validatePackage("$%$%#$%$#%#$%$#")).toBeFalsy();
        expect(validatePackage("node_modules")).toBeFalsy();
        expect(validatePackage("__proto__")).toBeFalsy();
        expect(validatePackage("favicon.ico")).toBeFalsy();
      });

      describe('validateName', () => {
        test('should fails with no string', () => {
          // intended to fail with Typescript, do not remove
          // @ts-ignore
          expect(validateName(null)).toBeFalsy();
          // @ts-ignore
          expect(validateName(undefined)).toBeFalsy();
        });

        test('good ones', () => {
          expect(validateName('verdaccio')).toBeTruthy();
          expect(validateName('some.weird.package-zzz')).toBeTruthy();
          expect(validateName('old-package@0.1.2.tgz')).toBeTruthy();
          // fix https://github.com/verdaccio/verdaccio/issues/1400
          expect(validateName('-build-infra')).toBeTruthy();
        });

        test('should be valid using uppercase', () => {
          expect(validateName('ETE')).toBeTruthy();
          expect(validateName('JSONStream')).toBeTruthy();
        });

        test('should fails with path seps', () => {
          expect(validateName('some/thing')).toBeFalsy();
          expect(validateName('some\\thing')).toBeFalsy();
        });

        test('should fail with no hidden files', () => {
          expect(validateName('.bin')).toBeFalsy();
        });

        test('should fails with reserved words', () => {
          expect(validateName('favicon.ico')).toBeFalsy();
          expect(validateName('node_modules')).toBeFalsy();
          expect(validateName('__proto__')).toBeFalsy();
        });

        test('should fails with other options', () => {
          expect(validateName('pk g')).toBeFalsy();
          expect(validateName('pk\tg')).toBeFalsy();
          expect(validateName('pk%20g')).toBeFalsy();
          expect(validateName('pk+g')).toBeFalsy();
          expect(validateName('pk:g')).toBeFalsy();
        });
      });
    });

    describe('validateMetadata', () => {
      test('should fills an empty metadata object', () => {
        // intended to fail with flow, do not remove
        // @ts-ignore
        expect(Object.keys(validateMetadata({}))).toContain(DIST_TAGS);
        // @ts-ignore
        expect(Object.keys(validateMetadata({}))).toContain('versions');
        // @ts-ignore
        expect(Object.keys(validateMetadata({}))).toContain('time');
      });

      test('should fails the assertions is not an object', () => {
        expect(function ( ) {
          // @ts-ignore
          validateMetadata('');
          // @ts-ignore
        }).toThrow(expect.hasAssertions());
      });

      test('should fails the assertions is name does not match', () => {
        expect(function ( ) {
          // @ts-ignore
          validateMetadata({}, "no-name");
          // @ts-ignore
        }).toThrow(expect.hasAssertions());
      });
    });

    describe('getVersionFromTarball', () => {
      test('should get the right version', () => {
        const simpleName = 'test-name-4.2.12.tgz'
        const complexName = 'test-5.6.4-beta.2.tgz'
        const otherComplexName = 'test-3.5.0-6.tgz'
        expect(getVersionFromTarball(simpleName)).toEqual('4.2.12')
        expect(getVersionFromTarball(complexName)).toEqual('5.6.4-beta.2')
        expect(getVersionFromTarball(otherComplexName)).toEqual('3.5.0-6')
      })

      test('should don\'n fall at incorrect tarball name', () => {
        expect(getVersionFromTarball('incorrectName')).toBeUndefined()
      })
    });
  });

  describe('String utilities', () => {
    test('should splice two strings and generate a url', () => {
      const url: string = spliceURL('http://domain.com', '/-/static/logo.png');

      expect(url).toMatch('http://domain.com/-/static/logo.png');
    });

    test('should splice a empty strings and generate a url', () => {
      const url: string = spliceURL('', '/-/static/logo.png');

      expect(url).toMatch('/-/static/logo.png');
    });

    test('should check HTTP protocol correctly', () => {
      expect(isHTTPProtocol('http://domain.com/-/static/logo.png')).toBeTruthy();
      expect(isHTTPProtocol('https://www.domain.com/-/static/logo.png')).toBeTruthy();
      expect(isHTTPProtocol('//domain.com/-/static/logo.png')).toBeTruthy();
      expect(isHTTPProtocol('file:///home/user/logo.png')).toBeFalsy();
      expect(isHTTPProtocol('file:///F:/home/user/logo.png')).toBeFalsy();
      // Note that uses ftp protocol in src was deprecated in modern browsers
      expect(isHTTPProtocol('ftp://1.2.3.4/home/user/logo.png')).toBeFalsy();
      expect(isHTTPProtocol('./logo.png')).toBeFalsy();
      expect(isHTTPProtocol('.\\logo.png')).toBeFalsy();
      expect(isHTTPProtocol('../logo.png')).toBeFalsy();
      expect(isHTTPProtocol('..\\logo.png')).toBeFalsy();
      expect(isHTTPProtocol('../../static/logo.png')).toBeFalsy();
      expect(isHTTPProtocol('..\\..\\static\\logo.png')).toBeFalsy();
      expect(isHTTPProtocol('logo.png')).toBeFalsy();
      expect(isHTTPProtocol('.logo.png')).toBeFalsy();
      expect(isHTTPProtocol('/static/logo.png')).toBeFalsy();
      expect(isHTTPProtocol('F:\\static\\logo.png')).toBeFalsy();
    });
  });

  describe('User utilities', () => {
    test('should generate gravatar url with email', () => {
      const gravatarUrl: string = generateGravatarUrl('user@verdaccio.org');

      expect(gravatarUrl).toMatch('https://www.gravatar.com/avatar/');
      expect(gravatarUrl).not.toMatch('000000000');
    });

    test('should generate generic gravatar url', () => {
      const gravatarUrl: string = generateGravatarUrl();

      expect(gravatarUrl).toMatch(GENERIC_AVATAR);
    });
  });

  describe('parseReadme', () => {
    test('should parse makrdown text to html template', () => {
      const markdown = '# markdown';
      expect(parseReadme('testPackage', markdown)).toEqual(
        '<h1 id="markdown">markdown</h1>'
      );
      expect(
        parseReadme('testPackage', String(readmeFile('markdown.md')))
      ).toMatchSnapshot();
    });

    test('should pass for conversion of non-ascii to markdown text', () => {
      const simpleText = 'simple text';
      const randomText = '%%%%%**##==';
      const randomTextMarkdown = 'simple text \n # markdown';

      expect(parseReadme('testPackage', randomText)).toEqual(
        '<p>%%%%%**##==</p>'
      );
      expect(parseReadme('testPackage', simpleText)).toEqual(
        '<p>simple text</p>'
      );
      expect(parseReadme('testPackage', randomTextMarkdown)).toEqual(
        '<p>simple text </p>\n<h1 id="markdown">markdown</h1>'
      );
    });

    test('should show error for no readme data', () => {
      const noData = '';
      const spy = jest.spyOn(logger, 'error');
      expect(parseReadme('testPackage', noData)).toEqual(
        '<p>ERROR: No README data found!</p>'
      );
      expect(spy).toHaveBeenCalledWith(
        { packageName: 'testPackage' },
        '@{packageName}: No readme found'
      );
    });
  });

  describe('addGravatarSupport', () => {
    test('check for blank object', () => {
      // @ts-ignore
      expect(addGravatarSupport({})).toEqual({});
    });

    test('author, contributors and maintainers fields are not present', () => {
      const packageInfo = {
        latest: {}
      };

      // @ts-ignore
      expect(addGravatarSupport(packageInfo)).toEqual(packageInfo);
    });

    test('author field is a blank object', () => {
      const packageInfo = { latest: { author: {} } };

      // @ts-ignore
      expect(addGravatarSupport(packageInfo)).toEqual(packageInfo);
    });

    test('author field is a string type', () => {
      const packageInfo = {
        latest: { author: 'user@verdccio.org' }
      };
      const result = {
        latest: {
          author: {
            author: 'user@verdccio.org',
            avatar: GENERIC_AVATAR,
            email: ''
          }
        }
      };

      // @ts-ignore
      expect(addGravatarSupport(packageInfo)).toEqual(result);
    });

    test('author field is an object type with author information', () => {
      const packageInfo = {
        latest: { author: { name: 'verdaccio', email: 'user@verdccio.org' } }
      };
      const result = {
        latest: {
          author: {
            avatar:
              'https://www.gravatar.com/avatar/794d7f6ef93d0689437de3c3e48fadc7',
            email: 'user@verdccio.org',
            name: 'verdaccio'
          }
        }
      };

      // @ts-ignore
      expect(addGravatarSupport(packageInfo)).toEqual(result);
    });

    test('contributor field is a blank array', () => {
      const packageInfo = {
        latest: {
          contributors: []
        }
      };

      // @ts-ignore
      expect(addGravatarSupport(packageInfo)).toEqual(packageInfo);
    });

    describe("contributors", () => {
      test('contributors field has contributors', () => {
        const packageInfo = {
          latest: {
            contributors: [
              { name: 'user', email: 'user@verdccio.org' },
              { name: 'user1', email: 'user1@verdccio.org' }
            ]
          }
        };

        const result = {
          latest: {
            contributors: [
              {
                avatar:
                  'https://www.gravatar.com/avatar/794d7f6ef93d0689437de3c3e48fadc7',
                email: 'user@verdccio.org',
                name: 'user'
              },
              {
                avatar:
                  'https://www.gravatar.com/avatar/51105a49ce4a9c2bfabf0f6a2cba3762',
                email: 'user1@verdccio.org',
                name: 'user1'
              }
            ]
          }
        };

        // @ts-ignore
        expect(addGravatarSupport(packageInfo)).toEqual(result);
      });

      test('contributors field is an object', () => {
        const packageInfo = {
          latest: {
            contributors: { name: 'user', email: 'user@verdccio.org' }
          }
        };

        const result = {
          latest: {
            contributors: [
              {
                avatar: 'https://www.gravatar.com/avatar/794d7f6ef93d0689437de3c3e48fadc7',
                email: 'user@verdccio.org',
                name: 'user'
              }
            ]
          }
        };

        // @ts-ignore
        expect(addGravatarSupport(packageInfo)).toEqual(result);
      });

      test('contributors field is a string', () => {
        const contributor = 'Barney Rubble <b@rubble.com> (http://barnyrubble.tumblr.com/)';
        const packageInfo = {
          latest: {
            contributors: contributor
          }
        };

        const result = {
          latest: {
            contributors: [
              {
                avatar: GENERIC_AVATAR,
                email: contributor,
                name: contributor
              }
            ]
          }
        };

        // @ts-ignore
        expect(addGravatarSupport(packageInfo)).toEqual(result);
      });
    });

    test('maintainers field is a blank array', () => {
      const packageInfo = {
        latest: {
          maintainers: []
        }
      };

      // @ts-ignore
      expect(addGravatarSupport(packageInfo)).toEqual(packageInfo);
    });

    test('maintainers field has maintainers', () => {
      const packageInfo = {
        latest: {
          maintainers: [
            { name: 'user', email: 'user@verdccio.org' },
            { name: 'user1', email: 'user1@verdccio.org' }
          ]
        }
      };

      const result = {
        latest: {
          maintainers: [
            {
              avatar:
                'https://www.gravatar.com/avatar/794d7f6ef93d0689437de3c3e48fadc7',
              email: 'user@verdccio.org',
              name: 'user'
            },
            {
              avatar:
                'https://www.gravatar.com/avatar/51105a49ce4a9c2bfabf0f6a2cba3762',
              email: 'user1@verdccio.org',
              name: 'user1'
            }
          ]
        }
      };

      // @ts-ignore
      expect(addGravatarSupport(packageInfo)).toEqual(result);
    });
  });

  describe('formatAuthor', () => {
    test('should check author field different values', () => {
      const author = 'verdaccioNpm';
      expect(formatAuthor(author).name).toEqual(author);
    });
    test('should check author field for object value', () => {
      const user = {
        name: 'Verdaccion NPM',
        email: 'verdaccio@verdaccio.org',
        url: 'https://verdaccio.org'
      };
      expect(formatAuthor(user).url).toEqual(user.url);
      expect(formatAuthor(user).email).toEqual(user.email);
      expect(formatAuthor(user).name).toEqual(user.name);
    });
    test('should check author field for other value', () => {
      expect(formatAuthor(null).name).toEqual(DEFAULT_USER);
      expect(formatAuthor({}).name).toEqual(DEFAULT_USER);
      expect(formatAuthor([]).name).toEqual(DEFAULT_USER);
    });
  });
});
