/* eslint-disable dot-notation */
/* eslint-disable no-plusplus */
import cheerio from 'cheerio';
import https from 'https';
import dns from 'dns';
import puppeteer from 'puppeteer';
import { createPaginationEmbed } from 'eris-pagination';
import { promisify } from 'util';
import axios, { AxiosError, AxiosResponse } from 'axios';
import { Message } from 'eris';
import { Client, Command, RichEmbed } from '../class';

interface TLSResponse {
  status: boolean,
  message?: string,
  subject: {
    commonName: string,
    organization: string[],
    organizationalUnit: string[],
    locality: string[],
    country: string[],
  },
  issuer: {
    commonName: string,
    organization: string[],
    organizationalUnit: string[],
    locality: string[],
    country: string[],
  },
  root: {
    commonName: string,
    organization: string[],
    organizationalUnit: string[],
    locality: string[],
    country: string[],
  },
  notBefore: Date,
  notAfter: Date,
  validationType: 'DV' | 'OV' | 'EV',
  signatureAlgorithm: string,
  publicKeyAlgorithm: string,
  serialNumber: string,
  keyUsage: number[],
  keyUsageAsText: ['CRL Signing'?, 'Certificate Signing'?, 'Content Commitment'?, 'Data Encipherment'?, 'Decipher Only'?, 'Digital Signature'?, 'Encipher Only'?, 'Key Agreement'?, 'Key Encipherment'?],
  extendedKeyUsage: number[],
  extendedKeyUsageAsText: ['All/Any Usages'?, 'TLS Web Server Authentication'?, 'TLS Web Client Authentication'?, 'Code Signing'?, 'E-mail Protection (S/MIME)'?],
  san: string[],
  fingerprint: string,
  connection: {
    cipherSuite: string,
    tlsVersion: 'SSLv3' | 'TLSv1' | 'TLSv1.1' | 'TLSv1.2' | 'TLSv1.3',
  },
}


export default class SiteInfo extends Command {
  constructor(client: Client) {
    super(client);
    this.name = 'site';
    this.description = 'Retrieves various information about a site. Includes web, host, and TLS information.';
    this.usage = `${this.client.config.prefix}tls <domain>\n*Only raw domain, no protocols or URLs.*`;
    this.aliases = ['ssl', 'cert', 'certinfo', 'ci', 'tls', 'si', 'siteinfo'];
    this.permissions = 0;
    this.guildOnly = true;
    this.enabled = false;
  }

  public async run(message: Message, args: string[]) {
    try {
      if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);

      const loading = await this.loading(message.channel, 'Loading...');

      let author: { name?: string, icon?: string, url?: string } = {};
      let s: AxiosResponse;
      try {
        s = await axios.get(`https://${args[0]}`, {
          httpsAgent: new https.Agent({
            rejectUnauthorized: false,
          }),
        });
      } catch (err) {
        loading.delete().catch(() => {});
        return this.error(message.channel, `Unable to retrieve information from site. | ${err}`);
      }
      try {
        const site = cheerio.load(s.data);

        let iconURI: string;

        try {
          await axios.get(`https://${args[0]}/favicon.ico`, {
            httpsAgent: new https.Agent({
              rejectUnauthorized: false,
            }),
          });
          iconURI = `https://${args[0]}/favicon.ico`;
        } catch {
          const att = site('link').toArray().filter((a) => a.attribs.rel === 'icon');
          if (att?.length > 0) {
            if (att[0].attribs.href.startsWith('/') || !att[0].attribs.href.startsWith('http')) {
              iconURI = `https://${args[0]}${att[0].attribs.href}`;
            } else {
              iconURI = att[0].attribs.href;
            }
          }
        }
        author = {
          name: site('title').text(),
          icon: iconURI,
          url: `https://${args[0]}`,
        };
      } catch {
        author = {
          name: `https://${args[0]}`,
          icon: `https://${args[0]}/favicon.ico`,
          url: `https://${args[0]}`,
        };
      }

      const embeds: RichEmbed[] = [];
      try {
        const server = await this.getServerInformation(args[0]);
        if (server) {
          const em = new RichEmbed();
          em.setTitle('Web Information');
          server.forEach((f) => em.addField(f.name, f.value, f.inline));
          embeds.push(em);
        }
        const host = await this.getHostInformation(args[0]);
        if (host) {
          const em = new RichEmbed();
          em.setTitle('Host Information');
          host.forEach((f) => em.addField(f.name, f.value, f.inline));
          embeds.push(em);
        }
        const tls = await this.getTLSInformation(args[0]);
        if (tls) {
          const em = new RichEmbed();
          em.setTitle('TLS Information');
          tls.forEach((f) => em.addField(f.name, f.value, f.inline));
          embeds.push(em);
        }
      } catch {
        loading.delete().catch(() => {});
        return this.error(message.channel, 'Unable to receive information.');
      }
      let screenshotData: string;
      try {
        const browser = await puppeteer.launch({
          ignoreHTTPSErrors: true,
          args: [
            '--ignore-certificate-errors',
            '--ignore-certificate-errors-spki-list',
          ],
        });
        const page = await browser.newPage();
        await page.goto(`https://${args[0]}`);
        screenshotData = await page.screenshot();
        browser.close();
      } catch (e) {
        this.client.util.signale.error(e);
      }
      embeds.forEach((embed) => {
        embed.setAuthor(author.name, author.icon, author.url);
        embed.setColor('#4870fe');
        embed.setFooter(this.client.user.username, this.client.user.avatarURL);
        embed.setTimestamp();
      });

      if (screenshotData) {
        if (embeds.length === 1) return message.channel.createMessage({ embed: embeds[0] }, { name: 'img.png', file: screenshotData });
      } else {
        await message.channel.createMessage('', { name: 'img.png', file: screenshotData });
      }
      loading.delete().catch(() => {});
      return await createPaginationEmbed(message, embeds, {
        cycling: true,
        extendedButtons: true,
      });
    } catch (err) {
      return this.client.util.handleError(err, message, this);
    }
  }

  public async getHostInformation(domain: string) {
    const getDNS = promisify(dns.resolve4);

    try {
      const dnsd = await getDNS(domain);
      if (dnsd?.length < 1) return null;

      const r = await axios.get(`http://ip-api.com/json/${dnsd[0]}`);

      const embed = new RichEmbed();
      embed.addField('IPv4 Address', dnsd[0], true);
      embed.addBlankField();
      embed.addField('Organization/Customer', r.data.org, true);
      embed.addField('Internet Service Provider', r.data.isp, true);
      embed.addField('Location', `${r.data.city}, ${r.data.regionName}, ${r.data.country}`, true);
      return embed.fields;
    } catch (err) {
      this.client.util.signale.error(err);
      return null;
    }
  }

  public async getServerInformation(domain: string) {
    let r: AxiosResponse;
    try {
      r = await axios.get(`https://${domain}`, {
        httpsAgent: new https.Agent({
          rejectUnauthorized: false,
        }),
      });
    } catch (err) {
      this.client.util.signale.error(err);
      return null;
    }

    const embed = new RichEmbed();
    embed.addField('Web Software', r.headers['server'] ?? 'N/A', true);
    embed.addField('Content Type', r.headers['content-type'] ? r.headers['content-type'].split(';')[0] : 'N/A', true);
    embed.addField('Content Length', r.headers['content-length'] ? this.client.util.dataConversion(r.headers['content-length']) : 'N/A', true);
    return embed.fields;
  }

  public async getTLSInformation(domain: string) {
    https.globalAgent.options.rejectUnauthorized = false;

    let r: AxiosResponse;
    try {
      r = await axios.get(`https://certapi.libraryofcode.org?q=${domain}`, {
        httpsAgent: new https.Agent({
          rejectUnauthorized: false,
        }),
      });
    } catch (err) {
      const error = <AxiosError>err;
      return null;
    }
    const resp: TLSResponse = r.data;

    const embed = new RichEmbed();

    let subjectString = `**Common Name:** ${resp.subject.commonName}\n`;
    if (resp.subject.organization?.length > 0) subjectString += `**Organization:** ${resp.subject.organization[0]}\n`;
    if (resp.subject.organizationalUnit?.length > 0) subjectString += `**Organizational Unit:** ${resp.subject.organizationalUnit[0]}\n`;
    if (resp.subject.locality?.length > 0) subjectString += `**Locality:** ${resp.subject.locality[0]}\n`;
    if (resp.subject.country?.length > 0) subjectString += `**Country:** ${resp.subject.country[0]}`;
    embed.addField('Subject', subjectString, true);

    let issuerString = `**Common Name:** ${resp.subject.commonName}\n`;
    if (resp.issuer.organization?.length > 0) issuerString += `**Organization:** ${resp.issuer.organization[0]}\n`;
    if (resp.issuer.organizationalUnit?.length > 0) issuerString += `**Organizational Unit:** ${resp.issuer.organizationalUnit[0]}\n`;
    if (resp.issuer.locality?.length > 0) issuerString += `**Locality:** ${resp.issuer.locality[0]}\n`;
    if (resp.issuer.country?.length > 0) issuerString += `**Country:** ${resp.issuer.country[0]}`;
    const rootString = `**Root Certificate:** ${resp.root.organization?.length > 0 ? resp.root.organization[0] : ''} (${resp.root.commonName ?? ''} : ${resp.root.organizationalUnit?.length > 0 ? resp.root.organizationalUnit[0] : ''})`;
    if (rootString?.length > 0) issuerString += `\n\n${rootString}`;
    embed.addField('Issuer', issuerString, true);

    embed.addBlankField();

    embed.addField('Issued On', new Date(resp.notBefore).toLocaleString('en-us'), true);
    embed.addField('Expiry', new Date(resp.notAfter).toLocaleString('en-us'), true);

    embed.addBlankField();

    let sanString = '';

    for (let i = 0; i < resp.san.length; i++) {
      if (i >= 10) {
        sanString += '...';
        break;
      }
      sanString += `${resp.san[i]}\n`;
    }

    embed.addField('Subject Alternative Names', sanString, true);

    embed.addBlankField();

    embed.addField('Key Usage', resp.keyUsageAsText.join(', '), true);
    embed.addField('Extended Key Usage', resp.extendedKeyUsageAsText.join(', '), true);
    embed.addField('Validation Type', resp.validationType, true);

    embed.addBlankField();

    embed.addField('Serial Number', String(resp.serialNumber), true);
    embed.addField('Fingerprint (SHA1)', resp.fingerprint, true);
    embed.addField('Signature Algorithm', resp.signatureAlgorithm, true);
    embed.addField('Public Key Algorithm', resp.publicKeyAlgorithm, true);

    embed.addBlankField();

    embed.addField('TLS Cipher Suite', resp.connection.cipherSuite, true);
    embed.addField('TLS Version', resp.connection.tlsVersion, true);

    https.globalAgent.options.rejectUnauthorized = true;
    return embed.fields;
  }
}
