// SPDX-License-Identifier: Apache 2

// NOTE: These tests were migrated from target_chains/ethereum/contracts/test/pyth.js but exclude the Wormhole-specific tests,
// which remain in the original JavaScript test file.

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "forge-std/Test.sol";

import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
import "@pythnetwork/pyth-sdk-solidity/PythErrors.sol";
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
import "../contracts/pyth/PythInternalStructs.sol";
import "../contracts/pyth/PythGovernanceInstructions.sol";
import "../contracts/pyth/PythUpgradable.sol";
import "../contracts/pyth/PythGetters.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "../contracts/wormhole/interfaces/IWormhole.sol";
import "../contracts/wormhole/Implementation.sol";
import "../contracts/wormhole/Setup.sol";
import "../contracts/wormhole/Wormhole.sol";
import "../contracts/wormhole-receiver/WormholeReceiver.sol";
import "../contracts/wormhole-receiver/ReceiverImplementation.sol";
import "../contracts/wormhole-receiver/ReceiverSetup.sol";
import "../contracts/wormhole-receiver/ReceiverGovernanceStructs.sol";
import "../contracts/wormhole-receiver/ReceiverStructs.sol";
import "../contracts/wormhole-receiver/ReceiverGovernance.sol";
import "../contracts/libraries/external/BytesLib.sol";
import "../contracts/pyth/mock/MockUpgradeableProxy.sol";
import "./utils/WormholeTestUtils.t.sol";
import "./utils/PythTestUtils.t.sol";
import "./utils/RandTestUtils.t.sol";

contract PythGovernanceTest is
    Test,
    WormholeTestUtils,
    PythTestUtils,
    PythGovernanceInstructions
{
    using BytesLib for bytes;

    IPyth public pyth;
    address constant TEST_SIGNER1 = 0xbeFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe;
    address constant TEST_SIGNER2 = 0x4ba0C2db9A26208b3bB1a50B01b16941c10D76db;
    uint16 constant TEST_GOVERNANCE_CHAIN_ID = 1;
    bytes32 constant TEST_GOVERNANCE_EMITTER =
        0x0000000000000000000000000000000000000000000000000000000000000011;
    uint16 constant TEST_PYTH2_WORMHOLE_CHAIN_ID = 1;
    bytes32 constant TEST_PYTH2_WORMHOLE_EMITTER =
        0x71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b;
    uint16 constant TARGET_CHAIN_ID = 2;

    function setUp() public {
        pyth = IPyth(setUpPyth(setUpWormholeReceiver(1)));
    }

    function testNoOwner() public {
        // Check that the ownership is renounced
        assertEq(OwnableUpgradeable(address(pyth)).owner(), address(0));
    }

    function testValidDataSources() public {
        assertTrue(
            PythGetters(address(pyth)).isValidDataSource(
                TEST_PYTH2_WORMHOLE_CHAIN_ID,
                TEST_PYTH2_WORMHOLE_EMITTER
            )
        );
    }

    function testSetFee() public {
        // Set fee to 5000 (5000 = 5 * 10^3)
        bytes memory setFeeMessage = abi.encodePacked(
            MAGIC,
            uint8(GovernanceModule.Target),
            uint8(GovernanceAction.SetFee),
            TARGET_CHAIN_ID,
            uint64(5), // value
            uint64(3) // exponent
        );

        bytes memory vaa = encodeAndSignMessage(
            setFeeMessage,
            TEST_GOVERNANCE_CHAIN_ID,
            TEST_GOVERNANCE_EMITTER,
            1
        );

        uint oldFee = PythGetters(address(pyth)).singleUpdateFeeInWei();
        vm.expectEmit(true, true, true, true);
        emit FeeSet(oldFee, 5000);

        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
        assertEq(PythGetters(address(pyth)).singleUpdateFeeInWei(), 5000);
    }

    function testSetValidPeriod() public {
        // Create governance VAA to set valid period to 0
        bytes memory data = abi.encodePacked(
            MAGIC,
            uint8(GovernanceModule.Target),
            uint8(GovernanceAction.SetValidPeriod),
            TARGET_CHAIN_ID, // Target chain ID
            uint64(0) // New valid period
        );

        bytes memory vaa = encodeAndSignMessage(
            data,
            TEST_GOVERNANCE_CHAIN_ID,
            TEST_GOVERNANCE_EMITTER,
            1
        );

        uint oldValidPeriod = PythGetters(address(pyth))
            .validTimePeriodSeconds();
        vm.expectEmit(true, true, true, true);
        emit ValidPeriodSet(oldValidPeriod, 0);

        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
        assertEq(PythGetters(address(pyth)).validTimePeriodSeconds(), 0);
    }

    function testInvalidGovernanceMessage() public {
        // Test with wrong magic number
        bytes memory data = abi.encodePacked(
            bytes4(0x12345678), // Wrong magic
            uint8(GovernanceModule.Target),
            uint8(GovernanceAction.SetValidPeriod),
            uint16(1), // Target chain ID
            uint64(0)
        );

        bytes memory vaa = encodeAndSignMessage(
            data,
            TEST_GOVERNANCE_CHAIN_ID,
            TEST_GOVERNANCE_EMITTER,
            1
        );

        vm.expectRevert(PythErrors.InvalidGovernanceMessage.selector);
        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
    }

    function testInvalidGovernanceTarget() public {
        // Test with wrong chain target
        bytes memory data = abi.encodePacked(
            MAGIC,
            uint8(GovernanceModule.Target),
            uint8(GovernanceAction.SetValidPeriod),
            uint16(3), // Different chain ID for testing invalid target
            uint64(0)
        );

        bytes memory vaa = encodeAndSignMessage(
            data,
            TEST_GOVERNANCE_CHAIN_ID,
            TEST_GOVERNANCE_EMITTER,
            1
        );

        vm.expectRevert(PythErrors.InvalidGovernanceTarget.selector);
        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
    }

    function testInvalidGovernanceDataSource() public {
        // Test with wrong emitter
        bytes memory data = abi.encodePacked(
            MAGIC,
            uint8(GovernanceModule.Target),
            uint8(GovernanceAction.SetValidPeriod),
            TARGET_CHAIN_ID,
            uint64(0)
        );

        bytes memory vaa = encodeAndSignMessage(
            data,
            TEST_GOVERNANCE_CHAIN_ID,
            bytes32(uint256(0x1111)), // Wrong emitter
            1
        );

        vm.expectRevert(PythErrors.InvalidGovernanceDataSource.selector);
        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
    }

    function testSetDataSources() public {
        // Create governance VAA to set new data sources
        bytes memory data = abi.encodePacked(
            MAGIC,
            uint8(GovernanceModule.Target),
            uint8(GovernanceAction.SetDataSources),
            TARGET_CHAIN_ID, // Target chain ID
            uint8(1), // Number of data sources
            uint16(1), // Chain ID
            bytes32(
                0x0000000000000000000000000000000000000000000000000000000000001111
            ) // Emitter
        );

        bytes memory vaa = encodeAndSignMessage(
            data,
            TEST_GOVERNANCE_CHAIN_ID,
            TEST_GOVERNANCE_EMITTER,
            1
        );

        PythInternalStructs.DataSource[] memory oldDataSources = PythGetters(
            address(pyth)
        ).validDataSources();

        PythInternalStructs.DataSource[]
            memory newDataSources = new PythInternalStructs.DataSource[](1);
        newDataSources[0] = PythInternalStructs.DataSource(
            1,
            0x0000000000000000000000000000000000000000000000000000000000001111
        );

        vm.expectEmit(true, true, true, true);
        emit DataSourcesSet(oldDataSources, newDataSources);

        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);

        // Verify old data source is no longer valid
        assertFalse(
            PythGetters(address(pyth)).isValidDataSource(
                TEST_PYTH2_WORMHOLE_CHAIN_ID,
                TEST_PYTH2_WORMHOLE_EMITTER
            )
        );

        // Verify new data source is valid
        assertTrue(
            PythGetters(address(pyth)).isValidDataSource(
                1,
                0x0000000000000000000000000000000000000000000000000000000000001111
            )
        );
    }

    function testSetWormholeAddress() public {
        // Deploy a new wormhole contract
        address newWormhole = address(setUpWormholeReceiver(1));

        // Create governance VAA to set new wormhole address
        bytes memory data = abi.encodePacked(
            MAGIC,
            uint8(GovernanceModule.Target),
            uint8(GovernanceAction.SetWormholeAddress),
            TARGET_CHAIN_ID, // Target chain ID
            newWormhole // New wormhole address
        );

        bytes memory vaa = encodeAndSignMessage(
            data,
            TEST_GOVERNANCE_CHAIN_ID,
            TEST_GOVERNANCE_EMITTER,
            1
        );

        address oldWormhole = address(PythGetters(address(pyth)).wormhole());
        vm.expectEmit(true, true, true, true);
        emit WormholeAddressSet(oldWormhole, newWormhole);

        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
        assertEq(address(PythGetters(address(pyth)).wormhole()), newWormhole);
    }

    function testTransferGovernanceDataSource() public {
        uint16 newEmitterChain = 2;
        bytes32 newEmitterAddress = 0x0000000000000000000000000000000000000000000000000000000000001111;

        // Create claim VAA from new governance
        bytes memory claimData = abi.encodePacked(
            MAGIC,
            uint8(GovernanceModule.Target),
            uint8(GovernanceAction.RequestGovernanceDataSourceTransfer),
            TARGET_CHAIN_ID, // Target chain ID
            uint32(1) // New governance index
        );

        bytes memory claimVaa = encodeAndSignMessage(
            claimData,
            newEmitterChain,
            newEmitterAddress,
            1
        );

        // Create authorize VAA from current governance
        bytes memory authData = abi.encodePacked(
            MAGIC,
            uint8(GovernanceModule.Target),
            uint8(GovernanceAction.AuthorizeGovernanceDataSourceTransfer),
            TARGET_CHAIN_ID, // Target chain ID
            claimVaa
        );

        bytes memory authVaa = encodeAndSignMessage(
            authData,
            TEST_GOVERNANCE_CHAIN_ID,
            TEST_GOVERNANCE_EMITTER,
            1
        );

        PythInternalStructs.DataSource
            memory oldDataSource = PythInternalStructs.DataSource(
                TEST_GOVERNANCE_CHAIN_ID,
                TEST_GOVERNANCE_EMITTER
            );
        PythInternalStructs.DataSource
            memory newDataSource = PythInternalStructs.DataSource(
                newEmitterChain,
                newEmitterAddress
            );

        vm.expectEmit(true, true, true, true);
        emit GovernanceDataSourceSet(oldDataSource, newDataSource, 1);

        PythGovernance(address(pyth)).executeGovernanceInstruction(authVaa);

        // Verify old governance can't execute instructions anymore
        bytes memory invalidData = abi.encodePacked(
            MAGIC,
            uint8(GovernanceModule.Target),
            uint8(GovernanceAction.SetValidPeriod),
            uint16(1), // Wrong chain ID for testing invalid target
            uint64(0)
        );

        bytes memory invalidVaa = encodeAndSignMessage(
            invalidData,
            TEST_GOVERNANCE_CHAIN_ID,
            TEST_GOVERNANCE_EMITTER,
            2
        );

        vm.expectRevert(PythErrors.InvalidGovernanceDataSource.selector);
        PythGovernance(address(pyth)).executeGovernanceInstruction(invalidVaa);
    }

    function testSequentialGovernanceMessages() public {
        // First governance message
        bytes memory data1 = abi.encodePacked(
            MAGIC,
            uint8(GovernanceModule.Target),
            uint8(GovernanceAction.SetValidPeriod),
            TARGET_CHAIN_ID, // Target chain ID
            uint64(10)
        );

        bytes memory vaa1 = encodeAndSignMessage(
            data1,
            TEST_GOVERNANCE_CHAIN_ID,
            TEST_GOVERNANCE_EMITTER,
            1
        );

        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa1);

        // Second governance message
        bytes memory data2 = abi.encodePacked(
            MAGIC,
            uint8(GovernanceModule.Target),
            uint8(GovernanceAction.SetValidPeriod),
            TARGET_CHAIN_ID, // Target chain ID
            uint64(20)
        );

        bytes memory vaa2 = encodeAndSignMessage(
            data2,
            TEST_GOVERNANCE_CHAIN_ID,
            TEST_GOVERNANCE_EMITTER,
            2
        );

        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa2);

        // Try to replay first message
        vm.expectRevert(PythErrors.OldGovernanceMessage.selector);
        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa1);

        // Try to replay second message
        vm.expectRevert(PythErrors.OldGovernanceMessage.selector);
        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa2);
    }

    function testUpgradeContractWithChainIdZeroIsInvalid() public {
        // Deploy a new PythUpgradable contract
        PythUpgradable newImplementation = new PythUpgradable();

        // Create governance VAA with chain ID 0 (unset)
        bytes memory data = abi.encodePacked(
            MAGIC,
            uint8(GovernanceModule.Target),
            uint8(GovernanceAction.UpgradeContract),
            uint16(0), // Chain ID 0 (unset)
            address(newImplementation) // New implementation address
        );

        bytes memory vaa = encodeAndSignMessage(
            data,
            TEST_GOVERNANCE_CHAIN_ID,
            TEST_GOVERNANCE_EMITTER,
            1
        );

        // Should revert with InvalidGovernanceTarget
        vm.expectRevert(PythErrors.InvalidGovernanceTarget.selector);
        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
    }

    // Helper function to get the second address from event data
    function getSecondAddressFromEventData(
        bytes memory data
    ) internal pure returns (address) {
        (, address secondAddr) = abi.decode(data, (address, address));
        return secondAddr;
    }

    function testUpgradeContractShouldWork() public {
        // Deploy a new PythUpgradable contract
        PythUpgradable newImplementation = new PythUpgradable();

        // Create governance VAA to upgrade the contract
        bytes memory data = abi.encodePacked(
            MAGIC,
            uint8(GovernanceModule.Target),
            uint8(GovernanceAction.UpgradeContract),
            TARGET_CHAIN_ID, // Valid target chain ID
            address(newImplementation) // New implementation address
        );

        bytes memory vaa = encodeAndSignMessage(
            data,
            TEST_GOVERNANCE_CHAIN_ID,
            TEST_GOVERNANCE_EMITTER,
            1
        );

        // Create a custom event checker for ContractUpgraded event
        // Since we only care about the newImplementation parameter
        vm.recordLogs();

        // Execute the governance instruction
        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);

        // Get emitted logs and check the event parameters
        Vm.Log[] memory entries = vm.getRecordedLogs();
        bool foundUpgradeEvent = false;

        for (uint i = 0; i < entries.length; i++) {
            // The event signature for ContractUpgraded
            bytes32 eventSignature = keccak256(
                "ContractUpgraded(address,address)"
            );

            if (entries[i].topics[0] == eventSignature) {
                // This is a ContractUpgraded event
                // Get just the new implementation address using our helper
                address recordedNewImplementation = getSecondAddressFromEventData(
                        entries[i].data
                    );

                // Check newImplementation
                assertEq(recordedNewImplementation, address(newImplementation));
                foundUpgradeEvent = true;
                break;
            }
        }

        // Make sure we found the event
        assertTrue(foundUpgradeEvent, "ContractUpgraded event not found");

        // Verify the upgrade worked by checking the magic number
        assertEq(
            PythUpgradable(address(pyth)).pythUpgradableMagic(),
            0x97a6f304
        );

        // Verify the implementation was upgraded to our new implementation
        // Access implementation using the ERC1967 storage slot
        address implAddr = address(
            uint160(
                uint256(
                    vm.load(
                        address(pyth),
                        0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc // ERC1967 implementation slot
                    )
                )
            )
        );
        assertEq(implAddr, address(newImplementation));
    }

    function testUpgradeContractToNonPythContractWontWork() public {
        // Deploy a mock upgradeable proxy that isn't a proper Pyth implementation
        MockUpgradeableProxy newImplementation = new MockUpgradeableProxy();

        // Create governance VAA to upgrade to an invalid implementation
        bytes memory data = abi.encodePacked(
            MAGIC,
            uint8(GovernanceModule.Target),
            uint8(GovernanceAction.UpgradeContract),
            TARGET_CHAIN_ID, // Valid target chain ID
            address(newImplementation) // Invalid implementation address
        );

        bytes memory vaa = encodeAndSignMessage(
            data,
            TEST_GOVERNANCE_CHAIN_ID,
            TEST_GOVERNANCE_EMITTER,
            1
        );

        // Should revert with no specific error message because the mock implementation
        // doesn't have the pythUpgradableMagic method
        vm.expectRevert();
        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
    }

    function testSetTransactionFee() public {
        // Set transaction fee to 1000 (1000 = 1 * 10^3)
        bytes memory setTransactionFeeMessage = abi.encodePacked(
            MAGIC,
            uint8(GovernanceModule.Target),
            uint8(GovernanceAction.SetTransactionFee),
            TARGET_CHAIN_ID,
            uint64(1), // value
            uint64(3) // exponent
        );

        bytes memory vaa = encodeAndSignMessage(
            setTransactionFeeMessage,
            TEST_GOVERNANCE_CHAIN_ID,
            TEST_GOVERNANCE_EMITTER,
            1
        );

        uint oldFee = PythGetters(address(pyth)).transactionFeeInWei();
        vm.expectEmit(true, true, true, true);
        emit TransactionFeeSet(oldFee, 1000);

        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
        assertEq(PythGetters(address(pyth)).transactionFeeInWei(), 1000);

        // Test that update fee includes transaction fee
        bytes[] memory updateData = new bytes[](0);
        assertEq(pyth.getUpdateFee(updateData), 1000);

        // Test that insufficient fee reverts
        vm.expectRevert(PythErrors.InsufficientFee.selector);
        pyth.updatePriceFeeds{value: 999}(updateData);

        // Test that sufficient fee works
        pyth.updatePriceFeeds{value: 1000}(updateData);
    }

    function encodeAndSignWormholeMessage(
        bytes memory data,
        uint16 emitterChainId,
        bytes32 emitterAddress,
        uint64 sequence,
        uint8 numGuardians
    ) internal view returns (bytes memory) {
        return
            generateVaa(
                uint32(block.timestamp),
                emitterChainId,
                emitterAddress,
                sequence,
                data,
                numGuardians
            );
    }

    function encodeAndSignMessage(
        bytes memory data,
        uint16 emitterChainId,
        bytes32 emitterAddress,
        uint64 sequence
    ) internal view returns (bytes memory) {
        return
            encodeAndSignWormholeMessage(
                data,
                emitterChainId,
                emitterAddress,
                sequence,
                1 // Number of guardians
            );
    }

    // Events
    event ContractUpgraded(
        address oldImplementation,
        address newImplementation
    );
    event GovernanceDataSourceSet(
        PythInternalStructs.DataSource oldDataSource,
        PythInternalStructs.DataSource newDataSource,
        uint64 initialSequence
    );
    event DataSourcesSet(
        PythInternalStructs.DataSource[] oldDataSources,
        PythInternalStructs.DataSource[] newDataSources
    );
    event FeeSet(uint oldFee, uint newFee);
    event ValidPeriodSet(uint oldValidPeriod, uint newValidPeriod);
    event WormholeAddressSet(
        address oldWormholeAddress,
        address newWormholeAddress
    );
    event TransactionFeeSet(uint oldFee, uint newFee);
}
