import path from "path";
import fs from "fs";
import os from "os";
import { execSync, exec } from "child_process";
import { Media } from "../lib/models.js";
import { knex } from "../lib/ormish.js";
import { defer } from "../lib/api.js";
import assert from "assert";
import ffmpeg from "fluent-ffmpeg";
import slugify from "slugify";
import { glob, mkdir_to, changed } from "../lib/builderator.js";

const default_glob = "./media/videos/**/*.mp4";

export const description = "Load the media into the database, creating screenshots.";

export const options = [
  [ "--screenshot_at <seconds_list>", "time where to get screenshot (comma list)", "40,10,0"
  ],
  [ "--force", "force recreating the torrents and screenshots.", false],
  [ "--input <glob>", "the files to process (glob)", default_glob]
];

export const screenshot = async (video_file, force=false, seek_time=40) => {
  // parse the filename and extract the date
  let pinfo = path.parse(video_file);
  const ffmpeg_defer = defer();

  const [target_dir, target_name] = [pinfo.dir.replace(/\\/g,"/").replace("/videos/","/images/"), pinfo.name];

  const screen_name = `${target_dir}/${target_name}.screen.jpg`;

  mkdir_to(screen_name);

  if(force || changed(video_file, screen_name)) {
    console.log("SCREEN", screen_name, "at time", seek_time);

    ffmpeg(video_file)
      .seekInput(seek_time)
      .frames(1)
      .size("?x720")
      .on("error", (err, stdout, stderr) => {
        console.error("FAIL", err.message);
      })
      .on("end", () => ffmpeg_defer.resolve())
      .save(screen_name);
  } else {
    ffmpeg_defer.resolve();
  }

  await ffmpeg_defer;
  return fs.existsSync(screen_name) ? screen_name : null;
}

export const check_video = (video_file) => {
  // this is about the dumbest thing you have to do to see if a video is streamable
  const stdout = exec(`ffprobe -v debug -print_format json ${video_file}`, {maxBuffer: 30 * 1048 * 1024}, (err, stdout, stderr) => {
    // ignore error because the command will overload stderr buffer because it's ffmpeg
    assert(stderr.includes('seeks:0'), `Video file ${video_file} does not have moov faststart.`);
  });
}

export const torrent = async (format, force=false) => {
  const { loader, media_servers } = await import("../lib/config.js");
  const torrent = `${format.filename}.torrent`;
  const tracker = loader.tracker;
  const video_file_url = format.filename.slice(1);
  const video_file = format.filename;
  const mktorrent = os.platform() === "win32" ? "wsl mktorrent" : "mktorrent";

  assert(loader.tracker, "You must set tracker in config/secrets.json:loader");
  assert(loader.tracker.startsWith("ws"), `Tracker in secrets/config.json needs to start with ws or wss, but is ${ loader.tracker }`);

  // mktorrent isn't on windows
  if(force || changed(video_file, torrent)) {
    console.log("TORRENT", torrent);
    if(fs.existsSync(torrent)) fs.unlinkSync(torrent);

    const web_seeds = media_servers.map(m => `-w "${m}${video_file_url}"`).join(" ");

    const stdout = execSync(`${mktorrent} -v ${web_seeds} -a "${tracker}" -o "${torrent}" -p -l 18 ${ video_file }`);
  }

  return torrent.slice(1);
}

export const video_meta = async (meta, opts={}) => {
  const fname = `.${meta.src}`;
  const stdout = execSync(`ffprobe -v quiet -show_format -show_streams -print_format json "${fname}"`);

  check_video(fname);

  Object.assign(meta, JSON.parse(stdout));
  // find the screenshots

  for(let seek_to of opts.screenshot_at) {
    meta.poster = await screenshot(meta.format.filename, opts.force, seek_to);

    if(meta.poster) {
      break;
    } else {
      console.log(`${fname}: No screenshot at ${ seek_to }.`);
    }
  }

  meta.format.ctime = fs.statSync(fname).ctime;
  meta.format.created_at = new Date(meta.format.ctime);
  meta.format.torrent = await torrent(meta.format, opts.force);

  return meta;
}

export const upsert_media = async(video, desc) => {
  const video_url = video.format.filename.slice(1)
  const stream = video.streams[0];
  const { name } = path.parse(video_url);

  const data = {
    "title": video.title,
    "description": video.description || desc,
    "created_at": video.format.created_at, // recorded at comes from the filename
    "updated_at": video.format.ctime, // ctime works better as the update time
    "src": video_url,
    "codec_name": stream.codec_name,
    "width": Number.parseInt(stream.width, 10),
    "height": Number.parseInt(stream.height, 10),
    "duration": Number.parseFloat(video.format.duration || stream.duration),
    "aspect_ratio": stream.display_aspect_ratio.replace(":", "/"),
    "preload": "auto",
    "torrent_url": video.format.torrent,
    "poster": video.poster ? video.poster.slice(1) : null,
    "slug": slugify(video.title || name, { lower: true, strict: true, trim: true})
  }

  // the db doesn't return the id unless it's an insert, which is stupid
  await Media.upsert(data, "src");

  // find the media we just updated
  const media = await Media.first({src: data.src});
  assert(media, `Failed to find media for ${data.src} that was just added.`);

  return media;
}

/**
 * Takes a comma separated list and returns an Array of ints, or
 * undefined if this is undefined.
 *
 * @param { string } screenshot_at comma list of seek times
 * @return { Array } List of times in seconds or undefined
 */
const parse_seek_times = (screenshot_at) => {
  if(screenshot_at) {
    const listing = screenshot_at.split(",").map(i => parseInt(i, 10));
    return listing;
  } else {
    return undefined;
  }
}

const load_media = async (opts) => {
  const media_files = glob(opts.input);

  opts.screenshot_at = parse_seek_times(opts.screenshot_at);

  for(let fname of media_files) {
    // remove the leading . for the src URL
    const src = fname.slice(1);

    // attempt to find this media in the database
    const original = await Media.first({src}) || { src };
    const video = await video_meta(original, opts);

    // use the episode number since ID could not represent the sequence
    const media = await upsert_media(video, undefined);

    assert(media !== undefined, `Failed to upsert media for ${fname}.`);
  }
}

export const main = async (opts) => {
  await load_media(opts);

  knex.destroy();
}
