import BondingRegistryModule from "../../ignition/modules/bondingRegistry";
import CiphernodeRegistryModule from "../../ignition/modules/ciphernodeRegistry";
import E3RefundManagerModule from "../../ignition/modules/e3RefundManager";
import InterfoldModule from "../../ignition/modules/interfold";
import InterfoldTicketTokenModule from "../../ignition/modules/interfoldTicketToken";
import InterfoldTokenModule from "../../ignition/modules/interfoldToken";
import MockCiphernodeRegistryModule from "../../ignition/modules/mockCiphernodeRegistry";
import mockComputeProviderModule from "../../ignition/modules/mockComputeProvider";
import MockDecryptionVerifierModule from "../../ignition/modules/mockDecryptionVerifier";
import MockE3ProgramModule from "../../ignition/modules/mockE3Program";
import MockPkVerifierModule from "../../ignition/modules/mockPkVerifier";
import MockCircuitVerifierModule from "../../ignition/modules/mockSlashingVerifier";
import MockStableTokenModule from "../../ignition/modules/mockStableToken";
import SlashingManagerModule from "../../ignition/modules/slashingManager";
import { BondingRegistry__factory as BondingRegistryFactory, CiphernodeRegistryOwnable__factory as CiphernodeRegistryOwnableFactory, E3RefundManager__factory as E3RefundManagerFactory, Interfold__factory as InterfoldFactory, InterfoldTicketToken__factory as InterfoldTicketTokenFactory, InterfoldToken__factory as InterfoldTokenFactory, MockBlacklistUSDC__factory as MockBlacklistUSDCFactory, MockCiphernodeRegistry__factory as MockCiphernodeRegistryFactory, MockCircuitVerifier__factory as MockCircuitVerifierFactory, MockDecryptionVerifier__factory as MockDecryptionVerifierFactory, MockE3Program__factory as MockE3ProgramFactory, MockPkVerifier__factory as MockPkVerifierFactory, MockUSDC__factory as MockUSDCFactory, SlashingManager__factory as SlashingManagerFactory, } from "../../types";
import { ethers, ignition, networkHelpers } from "./connection";
import { ADDRESS_ONE, BFV_PARAMS_DEFAULT, BFV_PARAMS_LARGE, DEFAULT_TIMEOUT_CONFIG, ENCRYPTION_SCHEME_ID, LICENSE_REQUIRED_BOND, MIN_TICKET_BALANCE, SEVEN_DAYS, SORTITION_SUBMISSION_WINDOW, THIRTY_DAYS, TICKET_PRICE, } from "./constants";
import { setupOperatorForSortition } from "./operators";
const { time, mine } = networkHelpers;
const abiCoder = ethers.AbiCoder.defaultAbiCoder();
/**
 * Deploy a fully-wired Interfold system and return typed handles. Call from a
 * spec's `setup()` and add only file-specific extras (extra signers,
 * additional thresholds, custom wiring, etc.).
 */
export async function deployInterfoldSystem(opts = {}) {
    const submissionWindow = opts.submissionWindow ?? SORTITION_SUBMISSION_WINDOW;
    const maxDuration = opts.maxDuration ?? THIRTY_DAYS;
    const timeoutConfig = opts.timeoutConfig ?? DEFAULT_TIMEOUT_CONFIG;
    const wireSlashingManager = opts.wireSlashingManager ?? true;
    const setupOperators = opts.setupOperators ?? 3;
    const bfvParams = opts.bfvParams === "large" ? BFV_PARAMS_LARGE : BFV_PARAMS_DEFAULT;
    const committeeThresholds = opts.committeeThresholds ??
        [
            [0, [1, 3]],
            [1, [2, 5]],
        ];
    // ── Signers ────────────────────────────────────────────────────────────────
    const signers = await ethers.getSigners();
    const [owner, notTheOwner] = signers;
    const ownerAddress = await owner.getAddress();
    if (setupOperators > signers.length - 2) {
        throw new Error(`setupOperators (${setupOperators}) exceeds available signers (${signers.length - 2})`);
    }
    const operators = [];
    for (let i = 0; i < setupOperators; i++) {
        operators.push(signers[2 + i]);
    }
    const treasury = opts.treasury && opts.treasury !== "owner" ? opts.treasury : owner;
    const treasuryAddress = await treasury.getAddress();
    const slashedFundsTreasury = opts.slashedFundsTreasury && opts.slashedFundsTreasury !== "owner"
        ? opts.slashedFundsTreasury
        : owner;
    const slashedFundsTreasuryAddress = await slashedFundsTreasury.getAddress();
    // ── Tokens ────────────────────────────────────────────────────────────────
    let usdcToken;
    if (opts.useBlacklistFeeToken) {
        const blacklistToken = await new MockBlacklistUSDCFactory(owner).deploy();
        await blacklistToken.waitForDeployment();
        // ABI-compatible with MockUSDC for the operations the fixture/spec needs.
        usdcToken = blacklistToken;
    }
    else {
        const { mockUSDC } = await ignition.deploy(MockStableTokenModule, {
            parameters: { MockUSDC: { initialSupply: 10_000_000 } },
        });
        usdcToken = MockUSDCFactory.connect(await mockUSDC.getAddress(), owner);
    }
    const { interfoldToken } = await ignition.deploy(InterfoldTokenModule, {
        parameters: { InterfoldToken: { owner: ownerAddress } },
    });
    const licenseToken = InterfoldTokenFactory.connect(await interfoldToken.getAddress(), owner);
    const { interfoldTicketToken } = await ignition.deploy(InterfoldTicketTokenModule, {
        parameters: {
            InterfoldTicketToken: {
                baseToken: await usdcToken.getAddress(),
                registry: ADDRESS_ONE,
                owner: ownerAddress,
            },
        },
    });
    const ticketToken = InterfoldTicketTokenFactory.connect(await interfoldTicketToken.getAddress(), owner);
    // ── Registry & Slashing ───────────────────────────────────────────────────
    const { slashingManager: _slashingManager } = await ignition.deploy(SlashingManagerModule, { parameters: { SlashingManager: { admin: ownerAddress } } });
    const slashingManager = SlashingManagerFactory.connect(await _slashingManager.getAddress(), owner);
    const { cipherNodeRegistry } = await ignition.deploy(CiphernodeRegistryModule, {
        parameters: {
            CiphernodeRegistry: {
                owner: ownerAddress,
                submissionWindow,
            },
        },
    });
    const ciphernodeRegistryAddress = await cipherNodeRegistry.getAddress();
    const ciphernodeRegistry = CiphernodeRegistryOwnableFactory.connect(ciphernodeRegistryAddress, owner);
    // Optional mock registry. When supplied, all wiring still uses the
    // mock's address (selectors are compatible). Tests can interact with
    // mock-specific helpers via `mockCiphernodeRegistry`.
    let mockCiphernodeRegistry;
    let effectiveRegistryAddress = ciphernodeRegistryAddress;
    if (opts.useMockCiphernodeRegistry) {
        const { mockCiphernodeRegistry: _mockReg } = await ignition.deploy(MockCiphernodeRegistryModule);
        const mockRegAddress = await _mockReg.getAddress();
        mockCiphernodeRegistry = MockCiphernodeRegistryFactory.connect(mockRegAddress, owner);
        effectiveRegistryAddress = mockRegAddress;
    }
    const { bondingRegistry: _bondingRegistry } = await ignition.deploy(BondingRegistryModule, {
        parameters: {
            BondingRegistry: {
                owner: ownerAddress,
                ticketToken: await ticketToken.getAddress(),
                licenseToken: await licenseToken.getAddress(),
                registry: effectiveRegistryAddress,
                slashedFundsTreasury: slashedFundsTreasuryAddress,
                ticketPrice: TICKET_PRICE,
                licenseRequiredBond: LICENSE_REQUIRED_BOND,
                minTicketBalance: MIN_TICKET_BALANCE,
                exitDelay: SEVEN_DAYS,
            },
        },
    });
    const bondingRegistry = BondingRegistryFactory.connect(await _bondingRegistry.getAddress(), owner);
    // ── Interfold ────────────────────────────────────────────────────────────────
    const { interfold: _interfold } = await ignition.deploy(InterfoldModule, {
        parameters: {
            Interfold: {
                owner: ownerAddress,
                maxDuration,
                registry: effectiveRegistryAddress,
                bondingRegistry: await bondingRegistry.getAddress(),
                e3RefundManager: ADDRESS_ONE, // placeholder — overridden below
                feeToken: await usdcToken.getAddress(),
                timeoutConfig,
            },
        },
    });
    const interfoldAddress = await _interfold.getAddress();
    const interfold = InterfoldFactory.connect(interfoldAddress, owner);
    const { e3RefundManager: _e3RefundManager } = await ignition.deploy(E3RefundManagerModule, {
        parameters: {
            E3RefundManager: {
                owner: ownerAddress,
                interfold: interfoldAddress,
                treasury: treasuryAddress,
            },
        },
    });
    const e3RefundManagerAddress = await _e3RefundManager.getAddress();
    const e3RefundManager = E3RefundManagerFactory.connect(e3RefundManagerAddress, owner);
    await interfold.setE3RefundManager(e3RefundManagerAddress);
    // ── Wire base contracts ───────────────────────────────────────────────────
    const registryAddress = await interfold.ciphernodeRegistry();
    if (registryAddress !== effectiveRegistryAddress) {
        await interfold.setCiphernodeRegistry(effectiveRegistryAddress);
    }
    // `setInterfold` / `setBondingRegistry` are present (matching selectors) on
    // both `CiphernodeRegistryOwnable` and `MockCiphernodeRegistry`.
    const registryForWiring = mockCiphernodeRegistry ?? ciphernodeRegistry;
    await registryForWiring.setInterfold(interfoldAddress);
    await registryForWiring.setBondingRegistry(await bondingRegistry.getAddress());
    await ticketToken.setRegistry(await bondingRegistry.getAddress());
    await bondingRegistry.setSlashingManager(await slashingManager.getAddress());
    await bondingRegistry.setRewardDistributor(interfoldAddress);
    await slashingManager.setBondingRegistry(await bondingRegistry.getAddress());
    if (wireSlashingManager) {
        await interfold.setSlashingManager(await slashingManager.getAddress());
        if (!mockCiphernodeRegistry) {
            await ciphernodeRegistry.setSlashingManager(await slashingManager.getAddress());
        }
        await slashingManager.setCiphernodeRegistry(effectiveRegistryAddress);
        await slashingManager.setInterfold(interfoldAddress);
        await slashingManager.setE3RefundManager(e3RefundManagerAddress);
    }
    // ── Mocks ─────────────────────────────────────────────────────────────────
    const { mockComputeProvider: _mockComputeProvider } = await ignition.deploy(mockComputeProviderModule);
    const mockComputeProvider = _mockComputeProvider;
    const { mockDecryptionVerifier: _mockDecryptionVerifier } = await ignition.deploy(MockDecryptionVerifierModule);
    const decryptionVerifier = MockDecryptionVerifierFactory.connect(await _mockDecryptionVerifier.getAddress(), owner);
    const { mockPkVerifier: _mockPkVerifier } = await ignition.deploy(MockPkVerifierModule);
    const pkVerifier = MockPkVerifierFactory.connect(await _mockPkVerifier.getAddress(), owner);
    const { mockE3Program: _mockE3Program } = await ignition.deploy(MockE3ProgramModule);
    const e3Program = MockE3ProgramFactory.connect(await _mockE3Program.getAddress(), owner);
    let circuitVerifier;
    if (opts.deployCircuitVerifier) {
        const { mockCircuitVerifier: _mockCircuitVerifier } = await ignition.deploy(MockCircuitVerifierModule);
        circuitVerifier = MockCircuitVerifierFactory.connect(await _mockCircuitVerifier.getAddress(), owner);
    }
    await interfold.enableE3Program(await e3Program.getAddress());
    await interfold.setParamSet(0, bfvParams);
    await interfold.setDecryptionVerifier(ENCRYPTION_SCHEME_ID, await decryptionVerifier.getAddress());
    await interfold.setPkVerifier(ENCRYPTION_SCHEME_ID, await pkVerifier.getAddress());
    // ── Committee thresholds ──────────────────────────────────────────────────
    for (const [size, [min, max]] of committeeThresholds) {
        await interfold.setCommitteeThresholds(size, [min, max]);
    }
    // ── Operators ─────────────────────────────────────────────────────────────
    await licenseToken.disableTransferRestrictions();
    if (operators.length > 0) {
        for (const operator of operators) {
            await setupOperatorForSortition(operator, bondingRegistry, licenseToken, usdcToken, ticketToken, 
            // The mock registry exposes `addCiphernode` as a no-op so the
            // helper still completes successfully; real specs use the owned
            // registry instance.
            (mockCiphernodeRegistry ?? ciphernodeRegistry));
        }
        await mine(1);
    }
    // ── End-user USDC mints ──────────────────────────────────────────────────
    const mintUsdcAmount = opts.mintUsdcAmount ?? ethers.parseUnits("1000000", 6);
    const mintUsdcTo = opts.mintUsdcTo ?? [owner, notTheOwner];
    for (const recipient of mintUsdcTo) {
        await usdcToken.mint(await recipient.getAddress(), mintUsdcAmount);
    }
    // ── Default request struct ───────────────────────────────────────────────
    const now = await time.latest();
    const inputWindowDuration = 300;
    const request = {
        committeeSize: 0, // Micro
        inputWindow: [now + 10, now + inputWindowDuration],
        e3Program: await e3Program.getAddress(),
        paramSet: 0,
        computeProviderParams: abiCoder.encode(["address"], [await decryptionVerifier.getAddress()]),
        customParams: abiCoder.encode(["address"], ["0x1234567890123456789012345678901234567890"]),
        proofAggregationEnabled: false,
    };
    return {
        interfold,
        ciphernodeRegistry,
        mockCiphernodeRegistry,
        bondingRegistry,
        slashingManager,
        e3RefundManager,
        licenseToken,
        ticketToken,
        usdcToken,
        mocks: {
            e3Program,
            decryptionVerifier,
            pkVerifier,
            mockComputeProvider,
            circuitVerifier,
        },
        owner,
        notTheOwner,
        operators,
        operator1: operators[0],
        operator2: operators[1],
        operator3: operators[2],
        treasury,
        slashedFundsTreasury,
        request,
    };
}
