use std::fs;
use std::process::Command;
use std::str;

use assert_cmd::prelude::*;
mod fixtures;
use fixtures::{Fixture, WranglerToml};

#[test]
fn it_builds_webpack() {
    let fixture = Fixture::new();
    fixture.scaffold_webpack();

    let wrangler_toml = WranglerToml::webpack_build("test-build-webpack");
    fixture.create_wrangler_toml(wrangler_toml);

    build_creates_assets(&fixture, vec!["script.js"]);
}

#[test]
fn it_builds_webpack_site() {
    let fixture = Fixture::new_site();

    let wrangler_toml = WranglerToml::site("test-build-site");
    fixture.create_wrangler_toml(wrangler_toml);

    build_creates_assets(&fixture, vec!["script.js"]);
}

#[test]
fn it_builds_webpack_site_with_custom_webpack() {
    let fixture = Fixture::new_site();

    fixture.create_file(
        "workers-site/webpack.worker.js",
        r#"
        module.exports = { entry: "./workers-site/index.js" };
    "#,
    );

    let mut wrangler_toml = WranglerToml::site("test-build-site-specify-config");
    wrangler_toml.webpack_config = Some("workers-site/webpack.worker.js");
    fixture.create_wrangler_toml(wrangler_toml);

    build_creates_assets(&fixture, vec!["script.js"]);
}

#[test]
fn it_builds_with_webpack_single_js() {
    let fixture = Fixture::new();
    fixture.create_file(
        "index.js",
        r#"
        addEventListener('fetch', event => {
            event.respondWith(handleRequest(event.request))
        })

        /**
        * Fetch and log a request
        * @param {Request} request
        */
        async function handleRequest(request) {
            return new Response('Hello worker!', { status: 200 })
        }
    "#,
    );
    fixture.create_default_package_json();

    let wrangler_toml = WranglerToml::webpack_build("test-build-webpack-single-js");
    fixture.create_wrangler_toml(wrangler_toml);

    build_creates_assets(&fixture, vec!["script.js"]);
}

#[test]
fn it_builds_with_webpack_function_config_js() {
    let fixture = Fixture::new();
    fixture.scaffold_webpack();

    fixture.create_file(
        "webpack.config.js",
        r#"
        module.exports = () => ({ entry: "./index.js" });
    "#,
    );

    let wrangler_toml = WranglerToml::webpack_std_config("test-build-webpack-function");
    fixture.create_wrangler_toml(wrangler_toml);

    build_creates_assets(&fixture, vec!["script.js"]);
}

#[test]
fn it_builds_with_webpack_promise_config_js() {
    let fixture = Fixture::new();
    fixture.scaffold_webpack();

    fixture.create_file(
        "webpack.config.js",
        r#"
        module.exports = Promise.resolve({ entry: "./index.js" });
    "#,
    );

    let wrangler_toml = WranglerToml::webpack_std_config("test-build-webpack-promise");
    fixture.create_wrangler_toml(wrangler_toml);

    build_creates_assets(&fixture, vec!["script.js"]);
}

#[test]
fn it_builds_with_webpack_function_promise_config_js() {
    let fixture = Fixture::new();
    fixture.scaffold_webpack();

    fixture.create_file(
        "webpack.config.js",
        r#"
        module.exports = Promise.resolve({ entry: "./index.js" });
    "#,
    );

    let wrangler_toml = WranglerToml::webpack_std_config("test-build-webpack-function-promise");
    fixture.create_wrangler_toml(wrangler_toml);

    build_creates_assets(&fixture, vec!["script.js"]);
}

#[test]
fn it_builds_with_webpack_specify_config() {
    let fixture = Fixture::new();
    fixture.scaffold_webpack();

    fixture.create_file(
        "webpack.worker.js",
        r#"
        module.exports = { entry: "./index.js" };
    "#,
    );

    let wrangler_toml = WranglerToml::webpack_custom_config(
        "test-build-webpack-specify-config",
        "webpack.worker.js",
    );
    fixture.create_wrangler_toml(wrangler_toml);

    build_creates_assets(&fixture, vec!["script.js"]);
}

#[test]
fn it_builds_with_webpack_single_js_missing_package_main() {
    let fixture = Fixture::new();
    fixture.create_empty_js();

    fixture.create_file(
        "package.json",
        r#"
        {
            "name": "webpack_single_js_missing_package_main"
        }
    "#,
    );

    let wrangler_toml =
        WranglerToml::webpack_build("test-build-webpack-single-js-missing-package-main");
    fixture.create_wrangler_toml(wrangler_toml);

    build_fails_with(
        &fixture,
        "The `main` key in your `package.json` file is required",
    );
}

#[test]
fn it_fails_with_multiple_webpack_configs() {
    let fixture = Fixture::new();
    fixture.scaffold_webpack();

    fixture.create_file(
        "webpack.config.js",
        r#"
        module.exports = [
            { entry: "./a.js" },
            { entry: "./b.js" }
        ]
    "#,
    );

    let wrangler_toml = WranglerToml::webpack_std_config("test-build-multiple-webpack-configs");
    fixture.create_wrangler_toml(wrangler_toml);

    build_fails_with(&fixture, "Multiple webpack configurations are not supported. You can specify a different path for your webpack configuration file in wrangler.toml with the `webpack_config` field");
}

#[test]
fn it_builds_with_webpack_wast() {
    let fixture = Fixture::new();
    fixture.create_file(
        "package.json",
        r#"
        {
            "dependencies": {
                "wast-loader": "^1.8.5"
            }
        }
    "#,
    );

    fixture.create_file(
        "index.js",
        r#"
        (async function() {
            await import("./module.wast");
        })()
    "#,
    );

    fixture.create_file(
        "webpack.config.js",
        r#"
        module.exports = {
            entry: "./index.js",
            module: {
                rules: [
                    {
                        test: /\.wast$/,
                        loader: "wast-loader",
                        type: "webassembly/experimental"
                    }
                ]
            },
        }
    "#,
    );

    fixture.create_file("module.wast", "(module)");

    let wrangler_toml = WranglerToml::webpack_std_config("test-build-webpack-wast");
    fixture.create_wrangler_toml(wrangler_toml);

    build_creates_assets(&fixture, vec!["script.js", "module.wasm"]);
}

#[test]
fn it_fails_with_webpack_target_node() {
    let fixture = Fixture::new();
    fixture.scaffold_webpack();

    fixture.create_file(
        "webpack.config.js",
        r#"
        module.exports = {
            "entry": "./index.js",
            "target": "node"
        }
    "#,
    );

    let wrangler_toml = WranglerToml::webpack_std_config("test-build-fails-webpack-target-node");
    fixture.create_wrangler_toml(wrangler_toml);

    build_fails_with(
        &fixture,
        "Building a Cloudflare Worker with target \"node\" is not supported",
    );
}

#[test]
fn it_fails_with_webpack_target_web() {
    let fixture = Fixture::new();
    fixture.scaffold_webpack();

    fixture.create_file(
        "webpack.config.js",
        r#"
        module.exports = {
            "entry": "./index.js",
            "target": "web"
        }
    "#,
    );

    let wrangler_toml = WranglerToml::webpack_std_config("test-build-fails-webpack-target-web");
    fixture.create_wrangler_toml(wrangler_toml);

    build_fails_with(
        &fixture,
        "Building a Cloudflare Worker with target \"web\" is not supported",
    );
}

#[test]
fn it_builds_with_webpack_target_webworker() {
    let fixture = Fixture::new();
    fixture.scaffold_webpack();

    fixture.create_file(
        "webpack.config.js",
        r#"
        module.exports = {
            "entry": "./index.js",
            "target": "webworker"
        }
    "#,
    );

    let wrangler_toml = WranglerToml::webpack_std_config("test-build-webpack-target-webworker");
    fixture.create_wrangler_toml(wrangler_toml);

    build_creates_assets(&fixture, vec!["script.js"]);
}

#[test]
fn it_builds_with_webpack_name_output() {
    let fixture = Fixture::new();
    fixture.scaffold_webpack();

    fixture.create_file(
        "webpack.config.js",
        r#"
        module.exports = {
            "entry": "./index.js",
            "devtool": "cheap-module-source-map"
        }
    "#,
    );
    fixture.create_file("index.js", "");

    let wrangler_toml = WranglerToml::webpack_std_config("test-build-webpack-name-output");
    fixture.create_wrangler_toml(wrangler_toml);

    build_creates_assets(&fixture, vec!["script.js"]);

    let out = fs::read_to_string(fixture.get_output_path().join("script.js")).unwrap();
    assert!(out.contains(r#"//# sourceMappingURL=worker.js.map"#));
}

#[test]
fn it_builds_with_webpack_name_output_warn() {
    let fixture = Fixture::new();
    fixture.scaffold_webpack();

    fixture.create_file(
        "webpack.config.js",
        r#"
        module.exports = {
            "entry": "./index.js",
            "output": {
                "filename": "hi.js"
            }
        }
    "#,
    );
    fixture.create_file("index.js", "");

    let wrangler_toml = WranglerToml::webpack_std_config("test-build-webpack-name-output-warn");
    fixture.create_wrangler_toml(wrangler_toml);

    let (_stdout, stderr) = build_creates_assets(&fixture, vec!["script.js"]);

    assert!(
        stderr.contains("webpack's output filename is being renamed"),
        "given: {}",
        stderr
    );
}

fn build_creates_assets_with_arg(
    fixture: &Fixture,
    script_names: Vec<&str>,
    args: Vec<&str>,
) -> (String, String) {
    let mut build = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap();
    build.current_dir(fixture.get_path());
    build.arg("build");
    build.args(args);

    let output = build.output().expect("failed to execute process");
    assert!(output.status.success(), "Build failed: {:?}", output);

    for script_name in script_names {
        assert!(fixture.get_output_path().join(script_name).exists());
    }

    (
        str::from_utf8(&output.stdout).unwrap().to_string(),
        str::from_utf8(&output.stderr).unwrap().to_string(),
    )
}

fn build_creates_assets(fixture: &Fixture, script_names: Vec<&str>) -> (String, String) {
    let mut build = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap();
    build.current_dir(fixture.get_path());
    build.arg("build");

    let output = build.output().expect("failed to execute process");
    assert!(output.status.success());

    for script_name in script_names {
        assert!(fixture.get_output_path().join(script_name).exists());
    }

    (
        str::from_utf8(&output.stdout).unwrap().to_string(),
        str::from_utf8(&output.stderr).unwrap().to_string(),
    )
}

fn build_fails_with(fixture: &Fixture, expected_message: &str) {
    let mut build = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap();
    build.current_dir(fixture.get_path());
    build.arg("build");

    let output = build.output().expect("failed to execute process");
    let stderr = str::from_utf8(&output.stderr).unwrap();
    eprintln!("{}", stderr);
    assert!(!output.status.success());
    assert!(
        stderr.contains(expected_message),
        "expected {:?} not found, given: {:?}",
        expected_message,
        str::from_utf8(&output.stderr)
    );
}

#[test]
fn it_builds_with_webpack_target_webworker_with_custom_file() {
    let fixture = Fixture::new();
    fixture.scaffold_webpack();

    fixture.create_file(
        "webpack.config.js",
        r#"
        module.exports = {
            "entry": "./index.js",
            "target": "webworker"
        }
    "#,
    );

    let wrangler_toml = WranglerToml::webpack_std_config("test-build-webpack-target-webworker");
    let file_name = "wrangler-custom.toml";
    fixture.create_file(file_name, &toml::to_string(&wrangler_toml).unwrap());

    build_creates_assets_with_arg(&fixture, vec!["script.js"], vec!["-c", file_name]);
}
