import cfg from "../config.mjs";
import db from "../sql.mjs";
import lib from "../lib.mjs";
import { getLevel } from "../admin.mjs";
import queue from "../queue.mjs";
import autotagger from "../autotagger.mjs";
import fetch from "flumm-fetch";
import fs from "fs";
import path from "path";

const regex = {
  all:   /https?:\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?/gi,
  yt:    /(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\/?\?(?:\S*?&?v\=))|youtu\.be\/)([a-zA-Z0-9_-]{6,11})/gi,
  imgur: /(?:https?:)?\/\/(\w+\.)?imgur\.com\/(\S*)(\..{3,4})/i,
  fourchan: /https?:\/\/i\.4cdn\.org\/(\w+)\/(\d+)\.(\w{3,4})/i,
  instagram: /(?:https?:\/\/www\.)?instagram\.com\S*?\/(?:p|reel)\/(\w{11})\/?/im
};
const mediagroupids = new Set();

export default async bot => {

  return [{
    name: "parser",
    call: regex.all,
    active: true,
    f: e => {
      const links = e.message.match(regex.all)?.filter(link => !link.includes(cfg.main.url.domain)) || [];
      let repost;
      if(e.media)
        links.push(e.media);
      
      if(links.length === 0)
        return false;

      if(e.message.match(/\!i(gnore)?\b/))
        return false;

      if(!e.channel.includes("w0bm") && (!e.message.match(/\!w(0bm)?\b/i) && (typeof e.raw.forward_from == 'undefined')))
        return false;

      if(e.type === 'tg' && // proto: tg
        !e.message.match(/\!w(0bm)?\b/i) && // !w / !w0bm
        !e.raw.forward_date && // is forwarded?
        !mediagroupids.has(e.raw.media_group_id) // prepared mediagroup?
      ) {
        return false;
      }
      else if(e.raw.media_group_id && e.message.match(/\!w(0bm)?\b/i)) {
        mediagroupids.add(e.raw.media_group_id);
      }

      console.log(`parsing ${links.length} link${links.length > 1 ? "s" : ""}...`);

      links.forEach(async link => {
        //if(regex.imgur.test(link))
        //  return await e.reply(`fuck imgur... seriously`);

        // if(regex.instagram.test(link))
        //   await e.reply(`instagay`);

        // if(regex.yt.test(link))
        //   await e.reply(`hi yt here, I am gay this is why I am not giving you the file`);

        // check repost (link)
        repost = await queue.checkrepostlink(link);
        if(repost)
          return await e.reply(`repost motherf0cker (link): ${cfg.main.url.full}/${repost}`);

        // generate uuid
        const uuid = await queue.genuuid();

        const maxfilesize = (getLevel(e.user).level > 50 ? cfg.main.maxfilesize * cfg.main.adminmultiplier : cfg.main.maxfilesize);

        // read metadata
        let ext;
        if(link.match(regex.instagram)) {
          // is instagram
          try {
            // @flummi -> is there a variable for the actual work directory so it doesn't have to be hardcoded?
            const meta = JSON.parse((await queue.exec(`yt-dlp --proxy ${cfg.main.socks} -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' --skip-download --dump-json "${link}"`)).stdout);
            ext = meta.ext;
          } catch(err) {
            const tmphead = (await fetch(link, { method: "HEAD" })).headers["content-type"];
            // this can be undefined for unsupported mime types, but will be caught in the general mime check below
            ext = cfg.mimes[tmphead];
          }
        }
        else if(link.match(regex.imgur)) {
          // imghure
          ext = link.split('.').pop();
        }
        else if(link.match(regex.yt)) {
          //yt - fuck anti bot protection
          try {
            const meta = JSON.parse((await queue.exec(`yt-dlp --proxy ${cfg.main.socks} -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' -I 1 --skip-download --dump-json "${link}"`)).stdout);
            ext = meta.ext;
          } catch(err) {
            const tmphead = (await fetch(link, { method: "HEAD" })).headers["content-type"];
            // this can be undefined for unsupported mime types, but will be caught in the general mime check below
            ext = cfg.mimes[tmphead];
          }
        }
        else if(link.match(regex.fourchan)) {
          //4chan - fuck cloudflare :)
          try {
            const meta = JSON.parse((await queue.exec(`yt-dlp --proxy ${cfg.main.socks} -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' --skip-download --dump-json "${link}"`)).stdout);
            ext = meta.ext;
          } catch(err) {
            const tmphead = (await fetch(link, { method: "HEAD" })).headers["content-type"];
            // this can be undefined for unsupported mime types, but will be caught in the general mime check below
            ext = cfg.mimes[tmphead];
          }
        }
        else {
          // is not instagram aka everything else that is not a exception
          // not using proxy
          try {
            const meta = JSON.parse((await queue.exec(`yt-dlp -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' --skip-download --dump-json "${link}"`)).stdout);
            ext = meta.ext;
          } catch(err) {
            const tmphead = (await fetch(link, { method: "HEAD" })).headers["content-type"];
            // this can be undefined for unsupported mime types, but will be caught in the general mime check below
            ext = cfg.mimes[tmphead];
          }
        }

        if(!Object.values(cfg.mimes).includes(ext?.toLowerCase())) {
          return console.log('mime schmime ' + ext);
        }

        const msg = await e.reply(`[charging the f0cker] downloading: ${uuid}`, {
          disable_notification: true
        });

        // <download data>
        const start = new Date();
        let source;

        if(link.match(regex.instagram)) {
          try {
            // add --cookies <path-to-cookies-file> on local instance if you want to avoid getting rate limited or optionally use a socks proxy from a network that is not being detected as a public network
            source = (await queue.exec(`yt-dlp --proxy ${cfg.main.socks} -f 'bv*[height<=1080]+ba/b[height<=1080] / wv*+ba/w' "${link}" --max-filesize ${maxfilesize / 1024}k --postprocessor-args "ffmpeg:-bitexact" -o "./tmp/${uuid}.%(ext)s" --print after_move:filepath --merge-output-format "mp4"`)).stdout.trim();
          } catch(err) {
            if(e.type == 'tg')
              return await e.editMessageText(msg.result.chat.id, msg.result.message_id, "instagram dl error");
            return await e.reply("instagram dl error", err);
          }
        }
        else if(link.match(regex.imgur)) {
          // imghure via torsocks // needs torsocks setup so this is optional
          try {
            await queue.exec(`torsocks wget ${link} -O ./tmp/${uuid}.${ext}`);
            source = `./tmp/${uuid}.${ext}`;
          } catch(err) {
            console.error('err:', err);
            if(e.type == 'tg')
              return await e.editMessageText(msg.result.chat.id, msg.result.message_id, err);
            return await e.reply('something went wrong lol');
          }
        }
        else if(link.match(regex.yt)) {
          try {
            // add --cookies <path-to-cookies-file> on local instance if you want to avoid getting rate limited or optionally use a socks proxy from a network that is not being detected as a public network
            source = (await queue.exec(`yt-dlp --proxy ${cfg.main.socks} -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' "${link}" -I 1 --max-filesize ${maxfilesize / 1024}k --postprocessor-args "ffmpeg:-bitexact" -o "./tmp/${uuid}.%(ext)s" --print after_move:filepath --merge-output-format "mp4"`)).stdout.trim();
          } catch(err) {
            if(e.type == 'tg')
              return await e.editMessageText(msg.result.chat.id, msg.result.message_id, "yt dl error");
            return await e.reply("yt dl error", err);
          }
        }
        else if(link.match(regex.fourchan)) {
          // 4chan via proxy - fuck cloudflare
          try {
            source = (await queue.exec(`yt-dlp --proxy ${cfg.main.socks} -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' "${link}" --max-filesize ${maxfilesize / 1024}k --postprocessor-args "ffmpeg:-bitexact" -o "./tmp/${uuid}.%(ext)s" --print after_move:filepath --merge-output-format "mp4"`)).stdout.trim();
          } catch(err) {
            if(e.type == 'tg')
              return await e.editMessageText(msg.result.chat.id, msg.result.message_id, "yt dl error");
            return await e.reply("yt dl error", err);
          }
        }
        else {
          // everything except the exceptions
          try {
            source = (await queue.exec(`yt-dlp -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' "${link}" --max-filesize ${maxfilesize / 1024}k --postprocessor-args "ffmpeg:-bitexact" -o "./tmp/${uuid}.%(ext)s" --print after_move:filepath --merge-output-format "mp4"`)).stdout.trim();
          } catch(err) {
            console.error('err:', err);
            if(e.type == 'tg')
              return await e.editMessageText(msg.result.chat.id, msg.result.message_id, err);
            return await e.reply('something went wrong lol / check maxfilesize?');
          }
        }
        // </download data>

        if(!source) {
          if(e.type == 'tg')
            return await e.editMessageText(msg.result.chat.id, msg.result.message_id, "something went wrong lol");
          return await e.reply("something went wrong lol");
        }

        if(source.match(/larger than/)) {
          if(e.type == 'tg')
            return await e.editMessageText(msg.result.chat.id, msg.result.message_id, "too large lol");
          return await e.reply("too large lol");
        }
        const end = ~~((new Date() - start) / 1e3);

        // filesize check
        const size = fs.statSync(source).size;
        if(size > maxfilesize) {
          await fs.promises.unlink(source).catch(_=>{});
          if(e.type == 'tg')
            return await e.editMessageText(msg.result.chat.id, msg.result.message_id, `too large lol. (${lib.formatSize(size)} / ${lib.formatSize(maxfilesize)})`);
          return await e.reply(`too large lol. (${lib.formatSize(size)} / ${lib.formatSize(maxfilesize)})`);
        }
        
        // mime check
        let mime = (await queue.exec(`file --mime-type -b ${source}`)).stdout.trim();
        try {
          if(mime == 'video/x-matroska') { // mkv failsafe
            await queue.exec(`ffmpeg -i ./tmp/${uuid}.mkv -codec copy ./tmp/${uuid}.mp4`);
            await fs.promises.unlink(source).catch(_=>{});
            source = source.replace(/\.mkv$/, '.mp4');
            mime = 'video/mp4';
          }
          if(source.match(/\.opus$/)) { // opus failsafe
            await queue.exec(`ffmpeg -i ./tmp/${uuid}.opus -codec copy ./tmp/${uuid}.ogg`);
            await fs.promises.unlink(source);
            source = source.replace(/\.opus$/, '.ogg');
            mime = 'audio/ogg';
          }
        } catch(err) {
          await fs.promises.unlink(source).catch(_=>{});
          if(e.type == 'tg')
            return await e.editMessageText(msg.result.chat.id, msg.result.message_id, "something went wrong lol");
          return await e.reply("something went wrong lol");
        }

        if(!Object.keys(cfg.mimes).includes(mime)) {
          await fs.promises.unlink(source).catch(_=>{});
          if(e.type == 'tg')
            return await e.editMessageText(msg.result.chat.id, msg.result.message_id, `lol, go f0ck yourself (${mime})`);
          return await e.reply(`lol, go f0ck yourself (${mime})`);
        }

        // generate checksum
        const checksum = (await queue.exec(`sha256sum ${source}`)).stdout.trim().split(" ")[0];

        // check repost (checksum)
        repost = await queue.checkrepostsum(checksum);
        if(repost) {
          await fs.promises.unlink(source).catch(_=>{});
          if(e.type == 'tg')
            return await e.editMessageText(msg.result.chat.id, msg.result.message_id, `repost motherf0cker (checksum): ${cfg.main.url.full}/${repost}`);
          return await e.reply(`repost motherf0cker (checksum): ${cfg.main.url.full}/${repost}`);
        }

        const filename = path.basename(source);

        await fs.promises.copyFile(source, `./public/b/${filename}`);
        await fs.promises.unlink(source).catch(_=>{});

        // user alias
        let username = e.user.nick || e.user.username;
        const alias = (await db`
          select "user"."user"
          from "user_alias"
          join "user" on "user".id = user_alias.userid
          where lower(user_alias.alias) ilike ${username}
          limit 1
        `)?.[0]?.user;
        if(alias) {
          username = alias;
        }

        await db`
          insert into items ${
            db({
              src: e.media ? "" : link,
              dest: filename,
              mime: mime,
              size: size,
              checksum: checksum,
              username: username,
              userchannel: e.channel,
              usernetwork: e.network,
              stamp: ~~(new Date() / 1000),
              active: true
            }, 'src', 'dest', 'mime', 'size', 'checksum', 'username', 'userchannel', 'usernetwork', 'stamp', 'active')
          }
        `;

        const itemid = await queue.getItemID(filename);

        // generate thumbnail
        try {
          await queue.genThumbnail(filename, mime, itemid, link);
        } catch(err) {
          await queue.exec(`magick ./mugge.png ./public/t/${itemid}.webp`);
        }

        let speed = lib.calcSpeed(size, end);
        speed = !Number.isFinite(speed) ? "yes" : `${speed.toFixed(2)} Mbit/s`;

        // autotagger
        let tags = [];
        /*if(cfg.apis?.nsfw1 && mime.startsWith('image')) {
          const nsfw = await autotagger.isNSFW(filename, size);
          tags.push(nsfw ? 'nsfw' : 'sfw');
          if(nsfw)
            await queue.tagNSFW(itemid);
          else
            await queue.tagSFW(itemid);
        }*/

        let outputmsgirc = `[f0cked] link: ${cfg.main.url.full}/${itemid} | size: ${lib.formatSize(size)} | speed: ${speed}`;
        let outputmsgtg = `[f0cked] size: ${lib.formatSize(size)} | speed: ${speed}`;

        if(tags?.length > 0) {
          const tagstr = tags.join(', ');
          outputmsgirc += ` | tags: ${tagstr}`;
          outputmsgtg += ` | tags: ${tagstr}`;
        }

        if(e.type == 'tg') {
          await e.deleteMessage(msg.result.chat.id, msg.result.message_id);
          await e.reply(outputmsgtg, {
            reply_markup: JSON.stringify({
              inline_keyboard: [[
                { text: (await lib.hasTag(itemid, 1) ? '✓ ' : '') + 'sfw', callback_data: `b_sfw:${itemid}` },
                { text: (await lib.hasTag(itemid, 2) ? '✓ ' : '') + 'nsfw', callback_data: `b_nsfw:${itemid}` },
                { text: 'tags', callback_data: `b_tags:${itemid}` },
                { text: '❌ delete', callback_data: `b_delete:${itemid}` }
              ], [
                { text: `open f0ck #${itemid}`, url: `${cfg.main.url.full}/${itemid}` }
              ]]
            })
          });
        }
        else {
          await e.reply(outputmsgirc);
        }
      });
    }
  }];
};
