const test = require("node:test");
const assert = require("node:assert/strict");

const stego = require("../src/stego/engine");

function createPixels(width, height) {
    const pixels =
        Buffer.alloc(
            width * height * 4
        );

    // Give the test image valid RGBA values.
    for (
        let i = 0;
        i < pixels.length;
        i += 4
    ) {
        pixels[i] = 128;
        pixels[i + 1] = 128;
        pixels[i + 2] = 128;
        pixels[i + 3] = 255;
    }

    return pixels;
}


test(
    "encodes and decodes a text message",
    async () => {
        const width = 100;
        const height = 100;

        const pixels =
            createPixels(
                width,
                height
            );

        const originalMessage =
            "kokiiii is the best :3";

        const password =
            "kokithebestie";

        const encoded =
            await stego.encode({
                pixels,
                width,
                height,
                message:
                    originalMessage,
                password
            });

        const decoded =
            await stego.decode({
                pixels:
                    encoded.pixels,
                password
            });

        assert.equal(
            decoded.type,
            "message"
        );

        assert.equal(
            decoded.message,
            originalMessage
        );
    }
);


test(
    "encodes and decodes a file",
    async () => {
        const width = 200;
        const height = 200;

        const pixels =
            createPixels(
                width,
                height
            );

        const originalFile =
            Buffer.from(
                "This is a secret file.\n"
            );

        const encoded =
            await stego.encode({
                pixels,
                width,
                height,
                password:
                    "test-password",
                file: {
                    originalname:
                        "secret.txt",
                    mimetype:
                        "text/plain",
                    buffer:
                        originalFile
                }
            });

        const decoded =
            await stego.decode({
                pixels:
                    encoded.pixels,
                password:
                    "test-password"
            });

        assert.equal(
            decoded.type,
            "file"
        );

        assert.equal(
            decoded.filename,
            "secret.txt"
        );

        assert.equal(
            decoded.mimetype,
            "text/plain"
        );

        assert.deepEqual(
            decoded.file,
            originalFile
        );
    }
);


test(
    "rejects an incorrect password",
    async () => {
        const pixels =
            createPixels(
                100,
                100
            );

        const encoded =
            await stego.encode({
                pixels,
                width: 100,
                height: 100,
                message:
                    "secret message",
                password:
                    "correct-password"
            });

        await assert.rejects(
            () =>
                stego.decode({
                    pixels:
                        encoded.pixels,
                    password:
                        "wrong-password"
                }),
            {
                message:
                    "Invalid password or corrupted payload"
            }
        );
    }
);


test(
    "requires a password for encrypted payloads",
    async () => {
        const pixels =
            createPixels(
                100,
                100
            );

        const encoded =
            await stego.encode({
                pixels,
                width: 100,
                height: 100,
                message:
                    "encrypted secret",
                password:
                    "secret-password"
            });

        await assert.rejects(
            () =>
                stego.decode({
                    pixels:
                        encoded.pixels
                }),
            {
                message:
                    "Password is required for this payload"
            }
        );
    }
);


test(
    "rejects a missing password during encrypted encoding",
    async () => {
        /*
         * This test intentionally verifies that
         * passwordless encoding is now allowed.
         *
         * It should NOT reject.
         */
        const pixels =
            createPixels(
                100,
                100
            );

        const encoded =
            await stego.encode({
                pixels,
                width: 100,
                height: 100,
                message:
                    "no password"
            });

        assert.ok(
            encoded.pixels
        );
    }
);


test(
    "encodes and decodes a message without a password",
    async () => {
        const pixels =
            createPixels(
                100,
                100
            );

        const encoded =
            await stego.encode({
                pixels,
                width: 100,
                height: 100,
                message:
                    "passwordless secret"
            });

        const decoded =
            await stego.decode({
                pixels:
                    encoded.pixels
            });

        assert.equal(
            decoded.type,
            "message"
        );

        assert.equal(
            decoded.message,
            "passwordless secret"
        );
    }
);


test(
    "encodes and decodes a file without a password",
    async () => {
        const pixels =
            createPixels(
                200,
                200
            );

        const originalFile =
            Buffer.from(
                "passwordless file"
            );

        const encoded =
            await stego.encode({
                pixels,
                width: 200,
                height: 200,
                file: {
                    originalname:
                        "secret.txt",
                    mimetype:
                        "text/plain",
                    buffer:
                        originalFile
                }
            });

        const decoded =
            await stego.decode({
                pixels:
                    encoded.pixels
            });

        assert.equal(
            decoded.type,
            "file"
        );

        assert.equal(
            decoded.filename,
            "secret.txt"
        );

        assert.equal(
            decoded.mimetype,
            "text/plain"
        );

        assert.deepEqual(
            decoded.file,
            originalFile
        );
    }
);


test(
    "rejects when neither message nor file is supplied",
    async () => {
        const pixels =
            createPixels(
                100,
                100
            );

        await assert.rejects(
            () =>
                stego.encode({
                    pixels,
                    width: 100,
                    height: 100,
                    password:
                        "password"
                }),
            {
                message:
                    "Message or file is required"
            }
        );
    }
);


test(
    "rejects a payload that is too large",
    async () => {
        const pixels =
            createPixels(
                10,
                10
            );

        const hugeMessage =
            "A".repeat(10000);

        await assert.rejects(
            () =>
                stego.encode({
                    pixels,
                    width: 10,
                    height: 10,
                    message:
                        hugeMessage,
                    password:
                        "password"
                }),
            {
                message:
                    /Payload too large/
            }
        );
    }
);


test(
    "reports image capacity",
    () => {
        const capacity =
            stego.getCapacity(
                100,
                100
            );

        assert.equal(
            capacity,
            3740
        );
    }
);


test(
    "rejects a tampered image",
    async () => {
        const pixels =
            createPixels(
                100,
                100
            );

        const encoded =
            await stego.encode({
                pixels,
                width: 100,
                height: 100,
                message:
                    "secret message",
                password:
                    "test-password"
            });

        const tampered =
            Buffer.from(
                encoded.pixels
            );

        /*
         * Flip a bit inside the embedded
         * payload.
         */
        tampered[100] ^= 1;

        await assert.rejects(
            () =>
                stego.decode({
                    pixels:
                        tampered,
                    password:
                        "test-password"
                }),
            {
                message:
                    "Invalid password or corrupted payload"
            }
        );
    }
);


test(
    "rejects a corrupted payload",
    async () => {
        const pixels =
            createPixels(
                100,
                100
            );

        const encoded =
            await stego.encode({
                pixels,
                width: 100,
                height: 100,
                message:
                    "secret message",
                password:
                    "test-password"
            });

        const corrupted =
            Buffer.from(
                encoded.pixels
            );

        /*
         * Corrupt several embedded bits
         * in the payload area.
         */
        for (
            let i = 100;
            i < 110;
            i++
        ) {
            corrupted[i] ^= 1;
        }

        await assert.rejects(
            () =>
                stego.decode({
                    pixels:
                        corrupted,
                    password:
                        "test-password"
                }),
            {
                message:
                    "Invalid password or corrupted payload"
            }
        );
    }
);