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

import {Script, console} from "forge-std/Script.sol";
import {WETH10} from "../contracts/tokens/WETH10.sol";
import {CREATE2Factory} from "../contracts/utils/CREATE2Factory.sol";

/**
 * @title DeployWETH10ToExactAddress
 * @notice Deploy WETH10 to exact address 0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f using CREATE2
 * @dev This script uses CREATE2 to deploy to the exact address specified in genesis.json
 */
contract DeployWETH10ToExactAddress is Script {
    // Target address from genesis.json
    address constant TARGET_WETH10 = 0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9F;
    
    function run() external {
        uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
        address deployer = vm.addr(deployerPrivateKey);
        
        console.log("Deploying WETH10 to exact address using CREATE2");
        console.log("Target address:", vm.toString(TARGET_WETH10));
        console.log("Deployer:", vm.toString(deployer));
        
        vm.startBroadcast(deployerPrivateKey);
        
        // Get WETH10 bytecode
        bytes memory weth10Bytecode = type(WETH10).creationCode;
        bytes32 bytecodeHash = keccak256(weth10Bytecode);
        console.log("WETH10 bytecode hash:", vm.toString(bytecodeHash));
        
        // Deploy CREATE2Factory (or use existing if already deployed)
        CREATE2Factory factory = new CREATE2Factory();
        address factoryAddress = address(factory);
        console.log("CREATE2Factory deployed at:", factoryAddress);
        
        // Check if contract already exists on-chain
        // Note: We'll try to interact with it - if it reverts, it doesn't exist
        try this.checkContractExists(TARGET_WETH10) returns (bool exists) {
            if (exists) {
                console.log("Contract already exists at target address");
                WETH10 weth10 = WETH10(payable(TARGET_WETH10));
                console.log("WETH10 name:", weth10.name());
                console.log("WETH10 symbol:", weth10.symbol());
                vm.stopBroadcast();
                return;
            }
        } catch {
            // Contract doesn't exist, continue with deployment
        }
        
        // Try multiple deployer addresses:
        // 1. Current deployer
        // 2. CREATE2Factory address (as deployer)
        // 3. Standard CREATE2 deterministic deployer
        // 4. Addresses from genesis.json
        address[] memory deployers = new address[](5);
        deployers[0] = deployer;
        deployers[1] = factoryAddress;
        deployers[2] = 0x4e59b44847b379578588920cA78FbF26c0B4956C; // Standard CREATE2 deployer
        deployers[3] = 0x0742D35CC6634c0532925A3b844bc9E7595f0Beb; // Genesis address with initial balance (padded)
        deployers[4] = 0xa55A4B57A91561e9df5a883D4883Bd4b1a7C4882; // Genesis address with high balance
        
        uint256 salt = 0;
        address foundDeployer = address(0);
        
        for (uint256 i = 0; i < deployers.length; i++) {
            console.log("Trying deployer:", vm.toString(deployers[i]));
            
            // Try common salts first
            salt = findSaltForAddress(deployers[i], weth10Bytecode, TARGET_WETH10);
            
            if (salt != 0) {
                foundDeployer = deployers[i];
                console.log("Found salt with common salts!");
                break;
            }
            
            // Try brute force
            console.log("Brute forcing salt (this may take a while)...");
            salt = bruteForceSalt(deployers[i], weth10Bytecode, TARGET_WETH10);
            
            if (salt != 0) {
                foundDeployer = deployers[i];
                console.log("Found salt via brute force!");
                break;
            }
        }
        
        if (salt != 0 && foundDeployer != address(0)) {
            console.log("Found salt:", vm.toString(salt));
            console.log("Using deployer:", vm.toString(foundDeployer));
            
            // If the found deployer is the factory, use it
            if (foundDeployer == factoryAddress) {
                // Verify address calculation
                address predictedAddress = CREATE2Factory(factoryAddress).computeAddress(weth10Bytecode, salt);
                console.log("Predicted address:", vm.toString(predictedAddress));
                
                if (predictedAddress == TARGET_WETH10) {
                    // Deploy using CREATE2Factory
                    address weth10Address = factory.deploy(weth10Bytecode, salt);
                    console.log("WETH10 deployed at:", weth10Address);
                    
                    require(weth10Address == TARGET_WETH10, "Address mismatch");
                    
                    // Verify deployment
                    WETH10 weth10 = WETH10(payable(weth10Address));
                    console.log("WETH10 name:", weth10.name());
                    console.log("WETH10 symbol:", weth10.symbol());
                    console.log("WETH10 decimals:", weth10.decimals());
                } else {
                    revert("Predicted address does not match target");
                }
            } else {
                // Need to deploy using the found deployer address directly
                console.log("Deployer address found, but not using factory.");
                console.log("You may need to deploy using the deployer address:", vm.toString(foundDeployer));
                console.log("Salt:", vm.toString(salt));
                revert("Deployer is not the factory - manual deployment may be required");
            }
        } else {
            revert("Could not find salt and deployer combination to produce target address");
        }
        
        vm.stopBroadcast();
        
        console.log("\n=== Deployment Summary ===");
        console.log("WETH10 Address:", vm.toString(TARGET_WETH10));
        console.log("CREATE2Factory:", vm.toString(factoryAddress));
        console.log("Salt:", vm.toString(salt));
    }
    
    function findSaltForAddress(
        address deployerAddr,
        bytes memory bytecode,
        address targetAddr
    ) internal pure returns (uint256) {
        // Try common salts
        bytes32 bytecodeHash = keccak256(bytecode);
        bytes32[] memory commonSalts = new bytes32[](10);
        commonSalts[0] = keccak256("WETH10");
        commonSalts[1] = keccak256("WETH");
        commonSalts[2] = keccak256("Wrapped Ether");
        commonSalts[3] = keccak256("ChainID-138-WETH10");
        commonSalts[4] = keccak256(abi.encodePacked("WETH10", uint256(138)));
        commonSalts[5] = bytes32(uint256(0)); // Zero salt
        commonSalts[6] = bytes32(uint256(1)); // Salt of 1
        commonSalts[7] = bytes32(uint256(138)); // Chain ID
        commonSalts[8] = keccak256(abi.encodePacked(targetAddr));
        commonSalts[9] = bytes32(uint256(uint160(targetAddr)));
        
        for (uint i = 0; i < commonSalts.length; i++) {
            bytes32 hash = keccak256(
                abi.encodePacked(bytes1(0xff), deployerAddr, commonSalts[i], bytecodeHash)
            );
            address computedAddr = address(uint160(uint256(hash)));
            if (computedAddr == targetAddr) {
                return uint256(commonSalts[i]);
            }
        }
        
        return 0;
    }
    
    function bruteForceSalt(
        address deployerAddr,
        bytes memory bytecode,
        address targetAddr
    ) internal pure returns (uint256) {
        // Enhanced brute force: checks more values and different salt patterns
        bytes32 bytecodeHash = keccak256(bytecode);
        
        // Try sequential salts first (0 to 10,000)
        for (uint256 i = 0; i < 10000; i++) {
            bytes32 salt = bytes32(i);
            bytes32 hash = keccak256(
                abi.encodePacked(bytes1(0xff), deployerAddr, salt, bytecodeHash)
            );
            address computedAddr = address(uint160(uint256(hash)));
            if (computedAddr == targetAddr) {
                return uint256(salt);
            }
        }
        
        // Try powers of 2 and common values
        uint256[] memory commonValues = new uint256[](20);
        commonValues[0] = 138; // Chain ID
        commonValues[1] = 1;
        commonValues[2] = 2;
        commonValues[3] = 10;
        commonValues[4] = 100;
        commonValues[5] = 1000;
        commonValues[6] = 10000;
        commonValues[7] = 100000;
        commonValues[8] = type(uint128).max;
        commonValues[9] = type(uint256).max;
        
        for (uint256 j = 10; j < 20; j++) {
            commonValues[j] = uint256(keccak256(abi.encodePacked("WETH10", j)));
        }
        
        for (uint256 k = 0; k < commonValues.length; k++) {
            bytes32 salt = bytes32(commonValues[k]);
            bytes32 hash = keccak256(
                abi.encodePacked(bytes1(0xff), deployerAddr, salt, bytecodeHash)
            );
            address computedAddr = address(uint160(uint256(hash)));
            if (computedAddr == targetAddr) {
                return uint256(salt);
            }
        }
        
        return 0;
    }
    
    // Helper function to check if contract exists
    function checkContractExists(address addr) external view returns (bool) {
        uint256 size;
        assembly {
            size := extcodesize(addr)
        }
        return size > 0;
    }
}

