#!/usr/bin/env babel-node

import fetch from "isomorphic-fetch";
import path from "path";
import fs from "fs-promise"
import imageDiff_ from "image-diff";
import https from "https";
import os from "os";

import { WebClient } from "@slack/client";

const CIRCLECI_TOKEN = process.env["CIRCLECI_TOKEN"];
const SLACK_TOKEN = process.env["SLACK_TOKEN"];
const SLACK_CHANNEL = "#ci-screenshots";

const CIRCLE_PROJECT = "github/metabase/metabase";
const CIRCLE_BRANCH = "master";

const CIRCLE_SCREENSHOT_PATH = "/home/ubuntu/metabase/screenshots/";

const slack = new WebClient(SLACK_TOKEN);

async function circleci(path) {
    const response = await fetch(
        `https://circleci.com/api/v1.1/${path}?circle-token=${encodeURIComponent(CIRCLECI_TOKEN)}`
    );
    return response.json();
}

function imageDiff(options) {
    return new Promise((resolve, reject) => {
        imageDiff_.getFullResult(options, (err, result) =>
            err ? reject(err) : resolve(result)
        );
    });
}

function download(url, path) {
    return new Promise((resolve, reject) => {
        https.get(url, response => {
            response.pipe(fs.createWriteStream(path)).on("finish", resolve);
        }).on('error', reject);
    });
}

async function getCircleArtifactScreenshots(buildPath) {
    let artifacts = await circleci(`project/${buildPath}/artifacts`);
    let results = {};
    for (const artifact of artifacts) {
        if (artifact.pretty_path.startsWith(CIRCLE_SCREENSHOT_PATH)) {
            const downloadPath = path.join(os.tmpdir(), path.basename(artifact.pretty_path));
            console.log("Downloading ", artifact.url, "to", downloadPath);
            await download(artifact.url, downloadPath);
            results[artifact.pretty_path.slice(CIRCLE_SCREENSHOT_PATH.length)] = downloadPath;
        }
    }
    return results;
}

async function getLocalScreenshots(directory) {
    const filenames = await fs.readdir(directory);
    let results = {};
    for (const filename of filenames) {
        results[filename] = path.resolve(directory, filename);
    }
    return results;
}

async function getScreenshots(target) {
    if (target.circleProject && target.circleBranch) {
        let builds = await circleci(`project/${target.circleProject}/tree/${target.circleBranch}`);
        let ok = builds.filter(build => build.status === "success" || build.status === "fixed");
        let screenshots = await getCircleArtifactScreenshots(`${target.circleProject}/${ok[0].build_num}`);
        return screenshots;
    } else if (target.localDirectory) {
        return await getLocalScreenshots(target.localDirectory);
    } else {
        throw "unknown target type";
    }
}

async function run(expectedTarget, actualTarget) {
    try {
        const expectedScreenshots = await getScreenshots(expectedTarget);
        console.log("Expected", Object.keys(expectedScreenshots));
        const actualScreenshots = await getScreenshots(actualTarget);
        console.log("Actual", Object.keys(expectedScreenshots));
        let images = Object.keys({ ...expectedScreenshots, ...actualScreenshots });

        for (const image of images) {
            const expectedImage = expectedScreenshots[image];
            const actualImage = actualScreenshots[image];
            const diffImage = path.join(os.tmpdir(), "diff-"+image);
            if (!actualImage) {
                console.log("Added", image);
                await slack.files.upload(image, {
                    title: "Added " + image,
                    channels: [SLACK_CHANNEL],
                    file: fs.createReadStream(actualImage)
                });
            } else if (!expectedImage) {
                console.log("Removed", image);
                await slack.files.upload(image, {
                    title: "Removed " + image,
                    channels: [SLACK_CHANNEL],
                    file: fs.createReadStream(expectedImage)
                });
            } else {
                const result = await imageDiff({
                    expectedImage,
                    actualImage,
                    diffImage,
                    shadow: true
                })
                if (result.percentage === 0.0) {
                    console.log("No difference", image);
                } else {
                    console.log("Changed", result.percentage.toFixed(2), image);
                    await slack.files.upload(image, {
                        title: "Diff (" + result.percentage.toFixed(2) + ") " + image,
                        channels: [SLACK_CHANNEL],
                        file: fs.createReadStream(diffImage)
                    });
                    await slack.files.upload(image, {
                        title: "Expected " + image,
                        channels: [SLACK_CHANNEL],
                        file: fs.createReadStream(expectedImage)
                    });
                    await slack.files.upload(image, {
                        title: "Actual " + image,
                        channels: [SLACK_CHANNEL],
                        file: fs.createReadStream(actualImage)
                    });
                }
            }
        }
    } catch (e) {
        console.error(e);
    }
}

run(
    { circleProject: CIRCLE_PROJECT, circleBranch: CIRCLE_BRANCH },
    { localDirectory: "screenshots" }
);
