import { expect } from "chai";
import hre, { deployments, waffle } from "hardhat";
import { BigNumber } from "ethers";
import "@nomiclabs/hardhat-ethers";
import { AddressZero } from "@ethersproject/constants";
import {
    defaultTokenCallbackHandlerContract,
    defaultTokenCallbackHandlerDeployment,
    deployContract,
    getSafeTemplate,
} from "../utils/setup";
import { executeContractCallWithSigners } from "../../src/utils/execution";

describe("FallbackManager", async () => {
    const setupWithTemplate = deployments.createFixture(async ({ deployments }) => {
        await deployments.fixture();
        const source = `
        contract Mirror {
            function lookAtMe() public returns (bytes memory) {
                return msg.data;
            }

            function nowLookAtYou(address you, string memory howYouLikeThat) public returns (bytes memory) {
                return msg.data;
            }
        }`;
        const mirror = await deployContract(user1, source);
        return {
            safe: await getSafeTemplate(),
            mirror,
        };
    });

    const [user1, user2] = waffle.provider.getWallets();

    describe("setFallbackManager", async () => {
        it("is correctly set on deployment", async () => {
            const { safe } = await setupWithTemplate();
            const handler = await defaultTokenCallbackHandlerDeployment();

            // Check fallback handler
            await expect(
                await hre.ethers.provider.getStorageAt(safe.address, "0x6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d5"),
            ).to.be.eq("0x" + "".padStart(64, "0"));

            // Setup Safe
            await safe.setup([user1.address, user2.address], 1, AddressZero, "0x", handler.address, AddressZero, 0, AddressZero);

            // Check fallback handler
            await expect(
                await hre.ethers.provider.getStorageAt(safe.address, "0x6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d5"),
            ).to.be.eq("0x" + handler.address.toLowerCase().slice(2).padStart(64, "0"));
        });

        it("is correctly set", async () => {
            const { safe } = await setupWithTemplate();
            const handler = await defaultTokenCallbackHandlerDeployment();

            // Setup Safe
            await safe.setup([user1.address, user2.address], 1, AddressZero, "0x", AddressZero, AddressZero, 0, AddressZero);

            // Check fallback handler
            await expect(
                await hre.ethers.provider.getStorageAt(safe.address, "0x6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d5"),
            ).to.be.eq("0x" + "".padStart(64, "0"));

            await expect(executeContractCallWithSigners(safe, safe, "setFallbackHandler", [handler.address], [user1]))
                .to.emit(safe, "ChangedFallbackHandler")
                .withArgs(handler.address);

            // Check fallback handler
            await expect(
                await hre.ethers.provider.getStorageAt(safe.address, "0x6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d5"),
            ).to.be.eq("0x" + handler.address.toLowerCase().slice(2).padStart(64, "0"));
        });

        it("emits event when is set", async () => {
            const { safe } = await setupWithTemplate();
            const handler = await defaultTokenCallbackHandlerDeployment();

            // Setup Safe
            await safe.setup([user1.address, user2.address], 1, AddressZero, "0x", AddressZero, AddressZero, 0, AddressZero);

            // Check event
            await expect(executeContractCallWithSigners(safe, safe, "setFallbackHandler", [handler.address], [user1]))
                .to.emit(safe, "ChangedFallbackHandler")
                .withArgs(handler.address);
        });

        it("is called when set", async () => {
            const { safe } = await setupWithTemplate();
            const handler = await defaultTokenCallbackHandlerDeployment();
            const safeHandler = (await defaultTokenCallbackHandlerContract()).attach(safe.address);
            // Check that Safe is NOT setup
            await expect(await safe.getThreshold()).to.be.deep.eq(BigNumber.from(0));

            // Check unset callbacks
            await expect(safeHandler.callStatic.onERC1155Received(AddressZero, AddressZero, 0, 0, "0x")).to.be.reverted;

            // Setup Safe
            await safe.setup([user1.address, user2.address], 1, AddressZero, "0x", handler.address, AddressZero, 0, AddressZero);

            // Check callbacks
            await expect(await safeHandler.callStatic.onERC1155Received(AddressZero, AddressZero, 0, 0, "0x")).to.be.eq("0xf23a6e61");
        });

        it("sends along msg.sender on simple call", async () => {
            const { safe, mirror } = await setupWithTemplate();
            // Setup Safe
            await safe.setup([user1.address, user2.address], 1, AddressZero, "0x", mirror.address, AddressZero, 0, AddressZero);

            const tx = {
                to: safe.address,
                data: mirror.interface.encodeFunctionData("lookAtMe"),
            };
            // Check that mock works as handler
            const response = await user1.call(tx);
            expect(response).to.be.eq(
                "0x" +
                    "0000000000000000000000000000000000000000000000000000000000000020" +
                    "0000000000000000000000000000000000000000000000000000000000000018" +
                    "7f8dc53c" +
                    user1.address.slice(2).toLowerCase() +
                    "0000000000000000",
            );
        });

        it("sends along msg.sender on more complex call", async () => {
            const { safe, mirror } = await setupWithTemplate();
            // Setup Safe
            await safe.setup([user1.address, user2.address], 1, AddressZero, "0x", mirror.address, AddressZero, 0, AddressZero);

            const tx = {
                to: safe.address,
                data: mirror.interface.encodeFunctionData("nowLookAtYou", [user2.address, "pink<>black"]),
            };
            // Check that mock works as handler
            const response = await user1.call(tx);
            expect(response).to.be.eq(
                "0x" +
                    "0000000000000000000000000000000000000000000000000000000000000020" +
                    "0000000000000000000000000000000000000000000000000000000000000098" +
                    // Function call
                    "b2a88d99" +
                    "000000000000000000000000" +
                    user2.address.slice(2).toLowerCase() +
                    "0000000000000000000000000000000000000000000000000000000000000040" +
                    "000000000000000000000000000000000000000000000000000000000000000b" +
                    "70696e6b3c3e626c61636b000000000000000000000000000000000000000000" +
                    user1.address.slice(2).toLowerCase() +
                    "0000000000000000",
            );
        });

        it("cannot be set to self", async () => {
            const { safe } = await setupWithTemplate();
            // Setup Safe
            await safe.setup([user1.address], 1, AddressZero, "0x", AddressZero, AddressZero, 0, AddressZero);

            // The transaction execution function doesn't bubble up revert messages so we check for a generic transaction fail code GS013
            await expect(executeContractCallWithSigners(safe, safe, "setFallbackHandler", [safe.address], [user1])).to.be.revertedWith(
                "GS013",
            );
        });
    });
});
