// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import 'test/base/BaseTest.t.sol';
import {ProxyAdmin} from 'contracts/misc/access/ProxyAdmin.sol';

contract MockContract {
    function testMockContract() public {
        // Prevents being counted in Foundry Coverage
    }

    function initialize(address initializationAddress) public pure {}
}

contract ProxyAdminTest is BaseTest {
    using stdJson for string;

    error Unauthorized();

    address controllerContract = makeAddr('CONTROLLER_CONTRACT');
    address proxyAdminContractOwner;

    function setUp() public override {
        super.setUp();

        loadOrDeploy_ProxyAdminContract();

        vm.prank(proxyAdmin);
        hubAsProxy.changeAdmin(address(proxyAdminContract));

        proxyAdminContractOwner = proxyAdminContract.owner();

        vm.prank(proxyAdminContractOwner);
        proxyAdminContract.setControllerContract(controllerContract);
    }

    // Negatives

    function testCannot_rollbackLastUpgrade_ifNotOwner(address otherAddress) public {
        vm.assume(otherAddress != proxyAdminContractOwner);
        vm.expectRevert('Ownable: caller is not the owner');
        vm.prank(otherAddress);
        proxyAdminContract.rollbackLastUpgrade();
    }

    function testCannot_changeLensHubProxyAdmin_ifNotOwner(address newAdmin, address otherAddress) public {
        vm.assume(otherAddress != proxyAdminContractOwner);
        vm.assume(newAdmin != address(0));
        vm.expectRevert('Ownable: caller is not the owner');
        vm.prank(otherAddress);
        proxyAdminContract.proxy_changeAdmin(newAdmin);
    }

    function testCannot_upgrade_ifNotOwnerOrControllerContract(address otherAddress) public {
        address hubImplV2 = address(new MockContract());

        vm.assume(otherAddress != proxyAdminContractOwner);
        vm.assume(otherAddress != controllerContract);

        vm.expectRevert(Unauthorized.selector);
        vm.prank(otherAddress);
        proxyAdminContract.proxy_upgrade(hubImplV2);
    }

    function testCannot_upgradeAndCall_ifNotOwnerOrControllerContract(address otherAddress) public {
        address hubImplV2 = address(new MockContract());

        vm.assume(otherAddress != proxyAdminContractOwner);
        vm.assume(otherAddress != controllerContract);

        vm.expectRevert(Unauthorized.selector);
        vm.prank(otherAddress);
        proxyAdminContract.proxy_upgradeAndCall(
            hubImplV2,
            abi.encodeWithSelector(MockContract.initialize.selector, address(0xdeadbeef))
        );
    }

    // Scenarios

    function testContructor() public notFork {
        assertEq(address(proxyAdminContract.LENS_HUB_PROXY()), address(hub), 'Hub address is not set correctly');
        assertEq(
            proxyAdminContract.previousImplementation(),
            address(hubImpl),
            'Hub implementation is not set correctly'
        );
        assertEq(proxyAdminContract.owner(), proxyAdminContractOwner, 'Owner is not set correctly');
        assertEq(proxyAdminContract.controllerContract(), controllerContract, 'Controller is not set correctly');
    }

    function testCurrentImplementation() public {
        assertEq(proxyAdminContract.currentImplementation(), address(hubImpl));
    }

    function testRollbackLastUpgrade() public {
        address hubImplV2 = address(new MockContract());

        address prevImpl;

        if (forkVersion == 1) {
            prevImpl = address(uint160(uint256(vm.load(hubProxyAddr, PROXY_IMPLEMENTATION_STORAGE_SLOT))));
            assertEq(prevImpl, proxyAdminContract.previousImplementation());

            vm.prank(proxyAdminContractOwner);
            proxyAdminContract.proxy_upgrade(hubImplV2);

            address newImpl = address(uint160(uint256(vm.load(hubProxyAddr, PROXY_IMPLEMENTATION_STORAGE_SLOT))));
            assertEq(newImpl, hubImplV2);
        } else {
            prevImpl = proxyAdminContract.previousImplementation();
        }

        vm.expectCall(address(hubAsProxy), abi.encodeCall(hubAsProxy.upgradeTo, (prevImpl)));

        vm.prank(proxyAdminContractOwner);
        proxyAdminContract.rollbackLastUpgrade();

        address rolledBackImpl = address(uint160(uint256(vm.load(hubProxyAddr, PROXY_IMPLEMENTATION_STORAGE_SLOT))));
        assertEq(rolledBackImpl, prevImpl);
    }

    function testChangeLensHubProxyAdmin(address newAdmin) public {
        vm.assume(newAdmin != address(0));
        address currentProxyAdmin = address(uint160(uint256(vm.load(hubProxyAddr, ADMIN_SLOT))));
        vm.assume(newAdmin != currentProxyAdmin);

        vm.expectCall(address(hubAsProxy), abi.encodeCall(hubAsProxy.changeAdmin, (newAdmin)));
        vm.prank(proxyAdminContractOwner);
        proxyAdminContract.proxy_changeAdmin(newAdmin);

        address changedProxyAdmin = address(uint160(uint256(vm.load(hubProxyAddr, ADMIN_SLOT))));
        assertEq(changedProxyAdmin, newAdmin);
    }

    function testUpgrade_ifCalledByOwner() public {
        address hubImplV2 = address(new MockContract());

        address prevImpl = address(uint160(uint256(vm.load(hubProxyAddr, PROXY_IMPLEMENTATION_STORAGE_SLOT))));
        assertTrue(prevImpl != hubImplV2);

        vm.expectCall(address(hubAsProxy), abi.encodeCall(hubAsProxy.upgradeTo, (hubImplV2)));
        vm.prank(proxyAdminContractOwner);
        proxyAdminContract.proxy_upgrade(hubImplV2);

        address upgradedImpl = address(uint160(uint256(vm.load(hubProxyAddr, PROXY_IMPLEMENTATION_STORAGE_SLOT))));
        assertEq(upgradedImpl, hubImplV2);

        assertEq(proxyAdminContract.previousImplementation(), prevImpl);
        assertEq(proxyAdminContract.controllerContract(), address(0)); // Controller is cleared after upgrade
    }

    function testUpgrade_ifCalledByControllerContract() public {
        address hubImplV2 = address(new MockContract());

        address prevImpl = address(uint160(uint256(vm.load(hubProxyAddr, PROXY_IMPLEMENTATION_STORAGE_SLOT))));
        assertTrue(prevImpl != hubImplV2);

        vm.expectCall(address(hubAsProxy), abi.encodeCall(hubAsProxy.upgradeTo, (hubImplV2)));
        vm.prank(controllerContract);
        proxyAdminContract.proxy_upgrade(hubImplV2);

        address upgradedImpl = address(uint160(uint256(vm.load(hubProxyAddr, PROXY_IMPLEMENTATION_STORAGE_SLOT))));
        assertEq(upgradedImpl, hubImplV2);

        assertEq(proxyAdminContract.previousImplementation(), prevImpl);
        assertEq(proxyAdminContract.controllerContract(), address(0)); // Controller is cleared after upgrade
    }

    function testUpgradeAndCall_ifCalledByOwner() public {
        address hubImplV2 = address(new MockContract());

        address prevImpl = address(uint160(uint256(vm.load(hubProxyAddr, PROXY_IMPLEMENTATION_STORAGE_SLOT))));
        assertTrue(prevImpl != hubImplV2);

        bytes memory data = abi.encodeCall(MockContract.initialize, (address(0xdeadbeef)));

        vm.expectCall(address(hubAsProxy), abi.encodeCall(hubAsProxy.upgradeToAndCall, (hubImplV2, data)));
        vm.expectCall(address(hubImplV2), data);
        vm.prank(proxyAdminContractOwner);
        proxyAdminContract.proxy_upgradeAndCall(hubImplV2, data);

        address upgradedImpl = address(uint160(uint256(vm.load(hubProxyAddr, PROXY_IMPLEMENTATION_STORAGE_SLOT))));
        assertEq(upgradedImpl, hubImplV2);

        assertEq(proxyAdminContract.previousImplementation(), prevImpl);
        assertEq(proxyAdminContract.controllerContract(), address(0)); // Controller is cleared after upgrade
    }

    function testUpgradeAndCall_ifCalledByControllerContract() public {
        address hubImplV2 = address(new MockContract());

        address prevImpl = address(uint160(uint256(vm.load(hubProxyAddr, PROXY_IMPLEMENTATION_STORAGE_SLOT))));
        assertTrue(prevImpl != hubImplV2);

        bytes memory data = abi.encodeCall(MockContract.initialize, (address(0xdeadbeef)));

        vm.expectCall(address(hubAsProxy), abi.encodeCall(hubAsProxy.upgradeToAndCall, (hubImplV2, data)));
        vm.expectCall(address(hubImplV2), data);
        vm.prank(controllerContract);
        proxyAdminContract.proxy_upgradeAndCall(hubImplV2, data);

        address upgradedImpl = address(uint160(uint256(vm.load(hubProxyAddr, PROXY_IMPLEMENTATION_STORAGE_SLOT))));
        assertEq(upgradedImpl, hubImplV2);

        assertEq(proxyAdminContract.previousImplementation(), prevImpl);
        assertEq(proxyAdminContract.controllerContract(), address(0)); // Controller is cleared after upgrade
    }
}
