import SAFE_MASTER_COPY_V1_ABI from "../../contracts/safe_master_copy_v1.json";
import ROLES_V1_ABI from "../../contracts/roles_v1.json";
import { Whitelist } from "../whitelist-class";
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
import { LedgerSigner } from "@anders-t/ethers-ledger";
// @ts-ignore
import { ethers } from "hardhat";
import {
  createMultisendTx,
  getPreValidatedSignatures,
  scopeTargetsV1,
} from "../../utils/util";
import {
  GAS_LIMIT,
  SAFE_OPERATION_DELEGATECALL,
  SECURITY_ROLE_ID_V1,
  tx,
} from "../../utils/constants";
import { getChainConfig } from "../../utils/roles-chain-config";
import { ChainConfig } from "../../utils/types";
import { ChainId } from "zodiac-roles-sdk/.";
import config from "../../env-config";
import type { RolesV1Interface } from "@gnosis-guild/zodiac/dist/esm/types/RolesV1";
import { Interface } from "ethers";

const ROLES_FUNCTIONS_ALLOWED = [
  "revokeTarget",
  "scopeTarget",
  "scopeAllowFunction",
  "scopeRevokeFunction",
  "scopeFunction",
  "scopeFunctionExecutionOptions",
  "scopeParameter",
  "scopeParameterAsOneOf",
  "unscopeParameter",
  "allowTarget",
] as const;

// Create an ethers Interface from the ABI to get function signatures
const rolesInterface = new Interface(ROLES_V1_ABI);

// this whitelisting class is used in the roles deployment so that security has the ability to scope functions
export class AccessControllerWhitelistV1 extends Whitelist {
  chainConfig: ChainConfig["v1"];
  constructor(acRolesAddr: string, caller: SignerWithAddress | LedgerSigner) {
    super(acRolesAddr, "v1", caller as any);
    const chainId = config.TENDERLY_FORK_ID;
    this.chainConfig = getChainConfig(chainId as ChainId, "v1");
  }

  // Allow the security team to call all the functions listed in `ROLES_FUNCTIONS_ALLOWED`on the investment roles modifier
  async getFullScope(invRolesAddr: string) {
    // Nested roles usage here can be confusing. The invRoles is the target that is scoped on the acRoles
    // Must scopeTarget before roles.scopeAllowFunction can be called
    const getScopedTargetTxs = await scopeTargetsV1(
      [invRolesAddr],
      SECURITY_ROLE_ID_V1,
      this.roles as any
    );
    // Get the sighashs that need to be whitelisted
    const functionSigs = ROLES_FUNCTIONS_ALLOWED.map(func => {
      // Find the function fragment in the ABI
      const fragment = rolesInterface.getFunction(func);
      if (!fragment) {
        throw new Error(`Function ${func} not found in ABI`);
      }
      return fragment.selector;
    });
    const getScopedAllowFunctionTxs = await this.scopeAllowFunctionsV1(
      invRolesAddr,
      functionSigs,
      SECURITY_ROLE_ID_V1
    );
    const txs = [...getScopedTargetTxs, ...getScopedAllowFunctionTxs];
    return createMultisendTx(txs, this.chainConfig.MULTISEND_ADDR);
  }

  async build(invRolesAddr: string, accessControlSafeAddr: string) {
    const metaTx = await this.getFullScope(invRolesAddr);
    const acSafe = new ethers.Contract(
      accessControlSafeAddr,
      SAFE_MASTER_COPY_V1_ABI,
      this.caller
    );
    const signature = getPreValidatedSignatures(await this.caller.getAddress());
    return await acSafe.execTransaction.populateTransaction(
      this.chainConfig.MULTISEND_ADDR,
      tx.zeroValue,
      metaTx.data,
      SAFE_OPERATION_DELEGATECALL,
      tx.avatarTxGas,
      tx.baseGas,
      tx.gasPrice,
      tx.gasToken,
      tx.refundReceiver,
      signature
    );
  }

  async execute(invRolesAddr: string, accessControlSafeAddr: string) {
    const populatedTx = await this.build(invRolesAddr, accessControlSafeAddr);
    const tx = await this.caller.sendTransaction({
      ...populatedTx,
      gasLimit: GAS_LIMIT,
    });
    console.log(
      "Successfully executed Security's access control related whitelisting"
    );
  }
}
