// SPDX-License-Identifier: Apache 2

pragma solidity ^0.8.0;

import "../../contracts/pyth/PythUpgradable.sol";
import "../../contracts/pyth/PythInternalStructs.sol";
import "../../contracts/pyth/PythAccumulator.sol";

import "../../contracts/libraries/MerkleTree.sol";

import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
import "@pythnetwork/pyth-sdk-solidity/IPythEvents.sol";
import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
import "@pythnetwork/pyth-sdk-solidity/PythUtils.sol";

import "forge-std/Test.sol";
import "./WormholeTestUtils.t.sol";
import "./RandTestUtils.t.sol";

abstract contract PythTestUtils is Test, WormholeTestUtils, RandTestUtils {
    uint16 constant SOURCE_EMITTER_CHAIN_ID = 0x1;
    bytes32 constant SOURCE_EMITTER_ADDRESS =
        0x71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b;

    uint16 constant GOVERNANCE_EMITTER_CHAIN_ID = 0x1;
    bytes32 constant GOVERNANCE_EMITTER_ADDRESS =
        0x0000000000000000000000000000000000000000000000000000000000000011;
    uint constant SINGLE_UPDATE_FEE_IN_WEI = 1;

    function setUpPyth(address wormhole) public returns (address) {
        PythUpgradable implementation = new PythUpgradable();
        ERC1967Proxy proxy = new ERC1967Proxy(
            address(implementation),
            new bytes(0)
        );
        PythUpgradable pyth = PythUpgradable(address(proxy));

        uint16[] memory emitterChainIds = new uint16[](1);
        emitterChainIds[0] = SOURCE_EMITTER_CHAIN_ID;

        bytes32[] memory emitterAddresses = new bytes32[](1);
        emitterAddresses[0] = SOURCE_EMITTER_ADDRESS;

        pyth.initialize(
            wormhole,
            emitterChainIds,
            emitterAddresses,
            GOVERNANCE_EMITTER_CHAIN_ID,
            GOVERNANCE_EMITTER_ADDRESS,
            0, // Initial governance sequence
            60, // Valid time period in seconds
            SINGLE_UPDATE_FEE_IN_WEI // single update fee in wei
        );

        return address(pyth);
    }

    function singleUpdateFeeInWei() public view returns (uint) {
        return SINGLE_UPDATE_FEE_IN_WEI;
    }

    /// Utilities to help generating price feed messages and VAAs for them

    struct PriceFeedMessage {
        bytes32 priceId;
        int64 price;
        uint64 conf;
        int32 expo;
        uint64 publishTime;
        uint64 prevPublishTime;
        int64 emaPrice;
        uint64 emaConf;
    }

    struct MerkleUpdateConfig {
        uint8 depth;
        uint8 numSigners;
        uint16 source_chain_id;
        bytes32 source_emitter_address;
        bool brokenVaa;
    }

    function encodePriceFeedMessages(
        PriceFeedMessage[] memory priceFeedMessages
    ) internal pure returns (bytes[] memory encodedPriceFeedMessages) {
        encodedPriceFeedMessages = new bytes[](priceFeedMessages.length);

        for (uint i = 0; i < priceFeedMessages.length; i++) {
            encodedPriceFeedMessages[i] = abi.encodePacked(
                uint8(PythAccumulator.MessageType.PriceFeed),
                priceFeedMessages[i].priceId,
                priceFeedMessages[i].price,
                priceFeedMessages[i].conf,
                priceFeedMessages[i].expo,
                priceFeedMessages[i].publishTime,
                priceFeedMessages[i].prevPublishTime,
                priceFeedMessages[i].emaPrice,
                priceFeedMessages[i].emaConf
            );
        }
    }

    function generateWhMerkleUpdateWithSource(
        PriceFeedMessage[] memory priceFeedMessages,
        MerkleUpdateConfig memory config
    ) internal returns (bytes memory whMerkleUpdateData) {
        bytes[] memory encodedPriceFeedMessages = encodePriceFeedMessages(
            priceFeedMessages
        );

        (bytes20 rootDigest, bytes[] memory proofs) = MerkleTree
            .constructProofs(encodedPriceFeedMessages, config.depth);

        bytes memory wormholePayload = abi.encodePacked(
            uint32(0x41555756), // PythAccumulator.ACCUMULATOR_WORMHOLE_MAGIC
            uint8(PythAccumulator.UpdateType.WormholeMerkle),
            uint64(0), // Slot, not used in target networks
            uint32(0), // Ring size, not used in target networks
            rootDigest
        );

        bytes memory wormholeMerkleVaa = generateVaa(
            0,
            config.source_chain_id,
            config.source_emitter_address,
            0,
            wormholePayload,
            config.numSigners
        );

        if (config.brokenVaa) {
            uint mutPos = getRandUint() % wormholeMerkleVaa.length;

            // mutate the random position by 1 bit
            wormholeMerkleVaa[mutPos] = bytes1(
                uint8(wormholeMerkleVaa[mutPos]) ^ 1
            );
        }

        whMerkleUpdateData = abi.encodePacked(
            uint32(0x504e4155), // PythAccumulator.ACCUMULATOR_MAGIC
            uint8(1), // major version
            uint8(0), // minor version
            uint8(0), // trailing header size
            uint8(PythAccumulator.UpdateType.WormholeMerkle),
            uint16(wormholeMerkleVaa.length),
            wormholeMerkleVaa,
            uint8(priceFeedMessages.length)
        );

        for (uint i = 0; i < priceFeedMessages.length; i++) {
            whMerkleUpdateData = abi.encodePacked(
                whMerkleUpdateData,
                uint16(encodedPriceFeedMessages[i].length),
                encodedPriceFeedMessages[i],
                proofs[i]
            );
        }
    }

    function generateWhMerkleUpdate(
        PriceFeedMessage[] memory priceFeedMessages,
        uint8 depth,
        uint8 numSigners
    ) internal returns (bytes memory whMerkleUpdateData) {
        whMerkleUpdateData = generateWhMerkleUpdateWithSource(
            priceFeedMessages,
            MerkleUpdateConfig(
                depth,
                numSigners,
                SOURCE_EMITTER_CHAIN_ID,
                SOURCE_EMITTER_ADDRESS,
                false
            )
        );
    }

    function generateForwardCompatibleWhMerkleUpdate(
        PriceFeedMessage[] memory priceFeedMessages,
        uint8 depth,
        uint8 numSigners,
        uint8 minorVersion,
        bytes memory trailingHeaderData
    ) internal returns (bytes memory whMerkleUpdateData) {
        bytes[] memory encodedPriceFeedMessages = encodePriceFeedMessages(
            priceFeedMessages
        );

        (bytes20 rootDigest, bytes[] memory proofs) = MerkleTree
            .constructProofs(encodedPriceFeedMessages, depth);
        // refactoring some of these generateWormhole functions was necessary
        // to workaround the stack too deep limit.
        bytes
            memory wormholeMerkleVaa = generateForwardCompatibleWormholeMerkleVaa(
                rootDigest,
                trailingHeaderData,
                numSigners
            );
        {
            whMerkleUpdateData = abi.encodePacked(
                generateForwardCompatibleWormholeMerkleUpdateHeader(
                    minorVersion,
                    trailingHeaderData
                ),
                uint16(wormholeMerkleVaa.length),
                wormholeMerkleVaa,
                uint8(priceFeedMessages.length)
            );
        }

        for (uint i = 0; i < priceFeedMessages.length; i++) {
            whMerkleUpdateData = abi.encodePacked(
                whMerkleUpdateData,
                uint16(encodedPriceFeedMessages[i].length),
                encodedPriceFeedMessages[i],
                proofs[i]
            );
        }
    }

    function generateForwardCompatibleWormholeMerkleUpdateHeader(
        uint8 minorVersion,
        bytes memory trailingHeaderData
    ) private returns (bytes memory whMerkleUpdateHeader) {
        whMerkleUpdateHeader = abi.encodePacked(
            uint32(0x504e4155), // PythAccumulator.ACCUMULATOR_MAGIC
            uint8(1), // major version
            minorVersion,
            uint8(trailingHeaderData.length), // trailing header size
            trailingHeaderData,
            uint8(PythAccumulator.UpdateType.WormholeMerkle)
        );
    }

    function generateForwardCompatibleWormholeMerkleVaa(
        bytes20 rootDigest,
        bytes memory futureData,
        uint8 numSigners
    ) internal returns (bytes memory wormholeMerkleVaa) {
        wormholeMerkleVaa = generateVaa(
            0,
            SOURCE_EMITTER_CHAIN_ID,
            SOURCE_EMITTER_ADDRESS,
            0,
            abi.encodePacked(
                uint32(0x41555756), // PythAccumulator.ACCUMULATOR_WORMHOLE_MAGIC
                uint8(PythAccumulator.UpdateType.WormholeMerkle),
                uint64(0), // Slot, not used in target networks
                uint32(0), // Ring size, not used in target networks
                rootDigest, // this can have bytes past this for future versions
                futureData
            ),
            numSigners
        );
    }

    function pricesToPriceFeedMessages(
        bytes32[] memory priceIds,
        PythStructs.Price[] memory prices
    ) public returns (PriceFeedMessage[] memory priceFeedMessages) {
        assertGe(priceIds.length, prices.length);
        priceFeedMessages = new PriceFeedMessage[](prices.length);

        for (uint i = 0; i < prices.length; ++i) {
            priceFeedMessages[i].priceId = priceIds[i];
            priceFeedMessages[i].price = prices[i].price;
            priceFeedMessages[i].conf = prices[i].conf;
            priceFeedMessages[i].expo = prices[i].expo;
            priceFeedMessages[i].publishTime = uint64(prices[i].publishTime);
            priceFeedMessages[i].prevPublishTime =
                uint64(prices[i].publishTime) -
                1;
            priceFeedMessages[i].emaPrice = prices[i].price;
            priceFeedMessages[i].emaConf = prices[i].conf;
        }
    }
}

contract PythUtilsTest is Test, WormholeTestUtils, PythTestUtils, IPythEvents {
    function testConvertToUnit() public {
        // Price can't be negative
        vm.expectRevert();
        PythUtils.convertToUint(-100, -5, 18);

        // Exponent can't be positive
        vm.expectRevert();
        PythUtils.convertToUint(100, 5, 18);

        // Price with 18 decimals and exponent -5
        assertEq(
            PythUtils.convertToUint(100, -5, 18),
            1000000000000000 // 100 * 10^13
        );

        // Price with 9 decimals and exponent -2
        assertEq(
            PythUtils.convertToUint(100, -2, 9),
            1000000000 // 100 * 10^7
        );

        // Price with 4 decimals and exponent -5
        assertEq(PythUtils.convertToUint(100, -5, 4), 10);

        // Price with 5 decimals and exponent -2
        // @note: We will lose precision here as price is
        // 0.00001 and we are targetDecimals is 2.
        assertEq(PythUtils.convertToUint(100, -5, 2), 0);
    }
}
