pub mod test_types;

#[cfg(test)]
mod test_round_trip {
    use serde::{de::DeserializeOwned, Serialize};

    use super::test_types::*;
    use std::{
        fmt::Debug,
        fs,
        io::Write,
        path::Path,
        process::{Command, Stdio},
    };

    #[cfg(target_os = "windows")]
    static TS_NODE_BIN: &str = "./node_modules/.bin/ts-node.cmd";
    #[cfg(not(target_os = "windows"))]
    static TS_NODE_BIN: &str = "./node_modules/.bin/ts-node";

    fn get_test_code(api_path: &Path, test_code: &str) -> String {
        format!(
            "
import * as api from '{}';
const assert = require('assert');
const getStdin = require('get-stdin');

process.on('unhandledRejection', (error: Error) => {{
    console.error(error);
    process.exit(1);
}});

function test(buffer: Buffer): api.Sink {{
    {}
}}

(async function main() {{
    let inBuf = await getStdin.buffer();
    let outBuf = test(inBuf).getUint8Array();
    process.stdout.write(outBuf);
}})();
        ",
            api_path
                .to_string_lossy()
                .replace("\\", "\\\\")
                .replace(".ts", ""),
            test_code
        )
    }

    fn generate_and_run<T>(test_type: T, test_code: &str)
    where
        T: PartialEq + Debug + Serialize + DeserializeOwned,
    {
        let dir = tempfile::tempdir_in("./tests").unwrap();
        let gen_path = dir.path().join("generated.ts");
        let test_path = dir.path().join("test.ts");

        bincode_typescript::from_file("./tests/test_types.rs", &gen_path, true).unwrap();

        let code = get_test_code(&gen_path, test_code);
        fs::write(&test_path, code).unwrap();

        let mut child = Command::new(TS_NODE_BIN)
            .args(&[test_path])
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .spawn()
            .expect("failed to spawn process");

        let child_stdin = child.stdin.as_mut().expect("failed to get stdin");

        let encoded: Vec<u8> = bincode::serialize(&test_type).unwrap();
        child_stdin.write_all(&encoded).unwrap();

        let output = child.wait_with_output().unwrap();
        assert!(output.status.success());

        let returned_type: T = bincode::deserialize(&output.stdout).unwrap();
        assert_eq!(test_type, returned_type);
    }

    #[test]
    fn unit_enum() {
        generate_and_run(
            UnitEnum::Three,
            "
        let val = api.readUnitEnum(buffer);
        let expected = api.UnitEnum.Three;
        assert.deepStrictEqual(val, expected);

        return api.writeUnitEnum(api.UnitEnum.Three);
        ",
        );
    }

    #[test]
    fn unit_enum_valued() {
        generate_and_run(
            UnitEnumNumbered::Eight,
            "
        let val = api.readUnitEnumNumbered(buffer);
        let expected = api.UnitEnumNumbered.Eight;
        assert.deepStrictEqual(val, expected);
        assert.deepStrictEqual(val, 8);

        return api.writeUnitEnumNumbered(expected);
        ",
        );
    }

    #[test]
    fn tuple_struct() {
        generate_and_run(
            TupleStruct(-145, vec![9987, 456]),
            "
        let val = api.readTupleStruct(buffer);
        let expected: api.TupleStruct = [-145, new Uint32Array([9987, 456])];
        assert.deepStrictEqual(val, expected);

        return api.writeTupleStruct(expected);
        ",
        );
    }

    #[test]
    fn named_struct_with_tuple() {
        generate_and_run(
            NamedStruct {
                zero: Some(28),
                one: 1.23,
                two: (128, UnitEnum::Three),
                three: "something".to_string(),
            },
            "
        let val = api.readNamedStruct(buffer);
        let expected = { 
            zero: 28, 
            one: 1.23, 
            two: [128, api.UnitEnum.Three] as [number, api.UnitEnum], 
            three: 'something' 
        };
        assert.deepStrictEqual(val, expected);

        return api.writeNamedStruct(expected);
        ",
        );
    }

    #[test]
    fn named_enum_variant() {
        generate_and_run(
            SomeEvent::Named {
                length: 34567,
                interval: 0.0001,
            },
            "
        let val = api.readSomeEvent(buffer);
        let expected = api.SomeEvent.Named({ length: BigInt(34567), interval: 0.0001 });
        assert.deepStrictEqual(val, expected);

        return api.writeSomeEvent(expected);
        ",
        );
    }

    #[test]
    fn unnamed_enum_many_numbers() {
        generate_and_run(
            SomeEvent::UnnamedMultiple(
                1,
                -2,
                3,
                -4,
                5,
                -6,
                1152921504606846976,
                -8152921504606846976,
                9,
                -10,
                11,
                -12,
                false,
            ),
            "
        let val = api.readSomeEvent(buffer);
        let expected = api.SomeEvent.UnnamedMultiple(
            1, 
            -2, 
            3, 
            -4, 
            5, 
            -6, 
            BigInt(1152921504606846976), 
            BigInt(-8152921504606846976), 
            BigInt(9), 
            BigInt(-10), 
            BigInt(11), 
            BigInt(-12), 
            false
        );
        assert.deepStrictEqual(val, expected);

        return api.writeSomeEvent(expected);
        ",
        );
    }

    #[test]
    fn named_struct_in_enum() {
        generate_and_run(
            SomeEvent::NamedStruct {
                inner: NamedStruct {
                    zero: None,
                    one: 1.23,
                    two: (128, UnitEnum::Three),
                    three: "something".to_string(),
                },
            },
            "
        let val = api.readSomeEvent(buffer);
        let expected = api.SomeEvent.NamedStruct({ 
            inner: { 
                zero: undefined,
                one: 1.23, 
                two: [128, api.UnitEnum.Three] as [number, api.UnitEnum], 
                three: 'something' 
            }
        });
        assert.deepStrictEqual(val, expected);

        return api.writeSomeEvent(expected);
        ",
        );
    }

    #[test]
    fn unnamed_optional_vec() {
        generate_and_run(
            SomeEvent::UnnamedOptVec(Some(vec![128; 1000])),
            "
        let val = api.readSomeEvent(buffer);
        let arr = new Uint8Array(1000);
        arr.fill(128);
        let expected = api.SomeEvent.UnnamedOptVec(arr);
        assert.deepStrictEqual(val, expected);

        return api.writeSomeEvent(expected);
        ",
        );
    }

    #[test]
    fn hashmap() {
        generate_and_run(
            SomeEvent::UnnamedHashMap(Some(
                vec![
                    ("One".to_string(), Some(UnitEnum::One)),
                    ("Two".to_string(), None),
                    ("Three".to_string(), Some(UnitEnum::Three)),
                ]
                .into_iter()
                .collect(),
            )),
            "
        let val = api.readSomeEvent(buffer);
        let expected = api.SomeEvent.UnnamedHashMap(
            new Map([
                ['One', api.UnitEnum.One], 
                ['Two', undefined],
                ['Three', api.UnitEnum.Three],
            ])
        );
        assert.deepStrictEqual(val, expected);

        return api.writeSomeEvent(expected);
        ",
        );
    }
}
