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

import { MainnetVerifier } from "../../../contracts/layer1/mainnet/MainnetVerifier.sol";
import { Inbox } from "src/layer1/core/impl/Inbox.sol";
import { ProverWhitelist } from "src/layer1/core/impl/ProverWhitelist.sol";
import { MainnetInbox } from "src/layer1/mainnet/MainnetInbox.sol";
import "src/layer1/preconf/impl/PreconfWhitelist.sol";
import { InsecureSgxVerifier } from "src/layer1/verifiers/InsecureSgxVerifier.sol";
import "src/layer1/verifiers/Risc0Verifier.sol";
import "src/layer1/verifiers/SP1Verifier.sol";
import { SecureSgxVerifier } from "src/layer1/verifiers/SecureSgxVerifier.sol";
import "src/shared/signal/SignalService.sol";
import "test/shared/DeployCapability.sol";

/// @title DeployShastaContracts
/// @notice Base contract for deploying Shasta contracts with configurable parameters.
abstract contract DeployShastaContracts is DeployCapability {
    struct VerifierAddresses {
        address sgxReth;
        address risc0;
        address sp1;
        address sgxGeth;
    }

    struct DeploymentConfig {
        address contractOwner;
        address proverManager;
        address ejectorManager;
        address activator;
        uint64 l2ChainId;
        address l1SignalService;
        address l2SignalService;
        address taikoToken;
        address sgxRethAutomataProxy;
        address sgxGethAutomataProxy;
        address r0Groth16Verifier;
        address sp1PlonkVerifier;
        address[] provers;
        address preconfWhitelist;
        address signalServicePauser;
        // When true, deploy the lenient InsecureSgxVerifier; otherwise deploy the strict
        // SgxVerifier. The secure default (false) selects the strict mainnet policy.
        bool useInsecureSgxPolicy;
    }

    modifier broadcast() {
        uint256 privateKey = vm.envUint("PRIVATE_KEY");
        require(privateKey != 0, "PRIVATE_KEY not set or invalid");
        vm.startBroadcast(privateKey);
        _;
        vm.stopBroadcast();
    }

    function run() external broadcast {
        DeploymentConfig memory config = _loadConfig();
        _validateConfig(config);
        _deploy(config);
    }

    /// @dev Override this function to provide deployment configuration.
    function _loadConfig() internal virtual returns (DeploymentConfig memory config);

    function _validateConfig(DeploymentConfig memory config) internal pure {
        require(config.contractOwner != address(0), "CONTRACT_OWNER not set");
        require(config.activator != address(0), "ACTIVATOR not set");
        require(config.l2ChainId != 0, "L2_CHAIN_ID not set");
        require(config.l1SignalService != address(0), "L1_SIGNAL_SERVICE not set");
        require(config.l2SignalService != address(0), "L2_SIGNAL_SERVICE not set");
        require(config.taikoToken != address(0), "TAIKO_TOKEN not set");
        require(config.sgxRethAutomataProxy != address(0), "SGX_AUTOMATA_PROXY not set");
        require(config.sgxGethAutomataProxy != address(0), "SGX_GETH_AUTOMATA_PROXY not set");
        require(config.r0Groth16Verifier != address(0), "R0_GROTH16_VERIFIER not set");
        require(config.sp1PlonkVerifier != address(0), "SP1_PLONK_VERIFIER not set");
        require(config.provers.length != 0, "PROVERS not set");
        require(config.preconfWhitelist != address(0), "PRECONF_WHITELIST not set");
        require(config.proverManager != address(0), "PROVER_MANAGER not set");
        require(config.ejectorManager != address(0), "EJECTOR_MANAGER not set");

        for (uint256 i = 0; i < config.provers.length; ++i) {
            require(config.provers[i] != address(0), "PROVERS contains zero address");
        }
    }

    function _deploy(DeploymentConfig memory config) internal {
        VerifierAddresses memory verifiers = _deployAllVerifiers(config);

        address proofVerifier = address(
            new MainnetVerifier(
                verifiers.sgxGeth, verifiers.sgxReth, verifiers.risc0, verifiers.sp1
            )
        );
        console2.log("MainnetVerifier deployed:", proofVerifier);

        address preconfWhitelist = address(new PreconfWhitelist(config.ejectorManager));
        console2.log("PreconfWhitelist deployed:", preconfWhitelist);

        // Set `msg.sender` as the owner by setting the owner to address(0)
        address proverWhitelist = deployProxy({
            name: "prover_whitelist",
            impl: address(new ProverWhitelist(config.proverManager)),
            data: abi.encodeCall(ProverWhitelist.init, address(0))
        });
        console2.log("ProverWhitelist deployed:", proverWhitelist);

        for (uint256 i = 0; i < config.provers.length; ++i) {
            console2.log("Add prover into ProverWhitelist:", config.provers[i]);
            ProverWhitelist(proverWhitelist).whitelistProver(config.provers[i], true);
        }
        Ownable2StepUpgradeable(proverWhitelist).transferOwnership(config.contractOwner);

        // We set the activator as the initial owner of the inbox to allow activation.
        // Ownership will be later transferred to the DAO.
        address shastaInbox = deployProxy({
            name: "shasta_inbox",
            impl: address(
                new MainnetInbox(
                    proofVerifier,
                    config.preconfWhitelist,
                    proverWhitelist,
                    config.l1SignalService,
                    config.taikoToken
                )
            ),
            data: abi.encodeCall(Inbox.init, config.activator)
        });
        console2.log("ShastaInbox deployed:", shastaInbox);

        address signalServiceImpl = address(
            new SignalService(shastaInbox, config.l2SignalService, config.signalServicePauser)
        );
        console2.log("New signalServiceImpl deployed:", signalServiceImpl);
    }

    function _deployAllVerifiers(DeploymentConfig memory config)
        private
        returns (VerifierAddresses memory verifiers)
    {
        // Mainnet AND all (public) testnets MUST use SecureSgxVerifier (strict TCB-status policy +
        // per-MRENCLAVE ATTRIBUTES pin); the strict SecureSgxVerifier is the secure default, and only
        // an explicit `useInsecureSgxPolicy` selects the lenient InsecureSgxVerifier, which relaxes
        // the TCB-status policy for lagging dev hardware and MUST be used by local devnets ONLY —
        // never by a public testnet or mainnet.
        // The registrar is set to address(0), leaving `registerInstance` permissionless; set a
        // non-zero registrar to restrict instance registration (a non-zero registrar may also
        // fail-close a compromised enclave via `removeEnclaveAttributePolicy`). The 24h
        // instance-validity delay gives off-chain monitoring time to evict a rogue self-registered
        // instance before it can prove (owner `addInstances` registrations are not delayed); it
        // applies to SecureSgxVerifier only.
        // NOTE: with registrar == address(0) the quote-freshness gate is enforced (permissionless
        // registration fails closed): `registerInstance` reverts with SGX_STALE_QUOTE unless the
        // prover embeds the recent-block commitment in reportData and the registration lands within
        // the 256-block window. Deploy with a non-zero registrar (or use owner `addInstances`) if
        // the prover does not embed the commitment yet.
        verifiers.sgxReth = _deploySgxVerifier(
            config.useInsecureSgxPolicy,
            config.l2ChainId,
            config.contractOwner,
            config.sgxRethAutomataProxy
        );
        console2.log("SgxVerifier deployed:", verifiers.sgxReth);

        verifiers.sgxGeth = _deploySgxVerifier(
            config.useInsecureSgxPolicy,
            config.l2ChainId,
            config.contractOwner,
            config.sgxGethAutomataProxy
        );
        console2.log("SgxGethVerifier deployed:", verifiers.sgxGeth);

        verifiers.risc0 = address(
            new Risc0Verifier(config.l2ChainId, config.r0Groth16Verifier, config.contractOwner)
        );
        console2.log("Risc0Verifier deployed:", verifiers.risc0);

        verifiers.sp1 = address(
            new SP1Verifier(config.l2ChainId, config.sp1PlonkVerifier, config.contractOwner)
        );
        console2.log("SP1Verifier deployed:", verifiers.sp1);
    }

    /// @dev Deploys an SGX verifier, selecting the strict SecureSgxVerifier by default and the
    /// lenient InsecureSgxVerifier only when `useInsecureSgxPolicy` is true. The registrar is set to
    /// address(0), leaving registerInstance permissionless — which also enforces the quote-freshness
    /// gate (see SgxVerifier.registerInstance): registration requires the prover to embed a
    /// recent-block commitment in reportData. SecureSgxVerifier is given a 24h instance-validity
    /// delay so off-chain monitoring can evict a rogue self-registered instance before it can prove
    /// (owner `addInstances` registrations are not delayed).
    function _deploySgxVerifier(
        bool useInsecureSgxPolicy,
        uint64 l2ChainId,
        address contractOwner,
        address automataProxy
    )
        private
        returns (address)
    {
        if (useInsecureSgxPolicy) {
            return
                address(
                    new InsecureSgxVerifier(l2ChainId, contractOwner, automataProxy, address(0))
                );
        }
        return address(
            new SecureSgxVerifier(l2ChainId, contractOwner, automataProxy, address(0), 24 hours)
        );
    }
}
