/**
 * @module EthereumContractService
 *
 * Provides a unified service for interacting with Ethereum smart contracts using ethers.js.
 */

// LiqETH Artifacts
import AccountingArtifact from '../../assets/ethereum/ABI/liqEth/accounting.sol/Accounting.json';
import DepositManagerArtifact from '../../assets/ethereum/ABI/liqEth/DepositManager.sol/DepositManager.json';
import LiqEthArtifact from '../../assets/ethereum/ABI/liqEth/liqEth.sol/LiqEthToken.json';
import StakingModuleArtifact from '../../assets/ethereum/ABI/liqEth/stakingModule.sol/StakingModule.json';
import WithdrawalQueueArtifact from '../../assets/ethereum/ABI/liqEth/withdrawalQueue.sol/WithdrawalQueue.json';
import WithdrawalVaultArtifact from '../../assets/ethereum/ABI/liqEth/withdrawalVault.sol/WithdrawalVault.json';
import BeaconStateArtifact from '../../assets/ethereum/ABI/liqEth/BeaconState.sol/BeaconState.json';
import YieldOracleArtifact from '../../assets/ethereum/ABI/liqEth/Yield.sol/YieldOracle.json';

// Outpost Artifacts
import DepositorArtifact from '../../assets/ethereum/ABI/outpost/Depositor.sol/Depositor.json';
import ReceiptNFTArtifact from '../../assets/ethereum/ABI/outpost/ReceiptNFT.sol/ReceiptNFT.json';
import OutpostManagerArtifact from '../../assets/ethereum/ABI/outpost/OutpostManager.sol/OutpostManager.json';
import BARArtifact from '../../assets/ethereum/ABI/outpost/BAR.sol/BAR.json';
import OPPArtifact from '../../assets/ethereum/ABI/outpost/OPP.sol/OPP.json';
import OPPCommonArtifact from '../../assets/ethereum/ABI/outpost/OPPCommon.sol/OPPCommon.json';
import OPPInboundArtifact from '../../assets/ethereum/ABI/outpost/OPPInbound.sol/OPPInbound.json';
import PretokenArtifact from '../../assets/ethereum/ABI/outpost/Pretoken.sol/Pretoken.json';
import AggregatorArtifact from '../../assets/ethereum/ABI/outpost/Aggregator.sol/Aggregator.json';
import EthUsdPriceConsumerArtifact from '../../assets/ethereum/ABI/outpost/EthUsdPriceConsumer.sol/EthUsdPriceConsumer.json';
import PoolArtifact from '../../assets/ethereum/ABI/outpost/Pool.sol/Pool.json';
import OutpostManagerAuthorityArtifact from '../../assets/ethereum/ABI/outpost/OutpostManagerAuthority.sol/OutpostManagerAuthority.json';

// Common Artifacts
import IodataArtifact from '../../assets/ethereum/ABI/common/iodata.sol/iodata.json';
import Base58Artifact from '../../assets/ethereum/ABI/common/Base58.sol/Base58.json';
import sysio_merkleArtifact from '../../assets/ethereum/ABI/common/sysio_merkle.sol/sysio_merkle.json';
import sysio_writeArtifact from '../../assets/ethereum/ABI/common/sysio_write.sol/sysio_write.json';

// ERC Token Artifacts
import ERC20Artifact from '../../assets/ethereum/ABI/token/ERC20Token.sol/ERC20Token.json';
import ERC721Artifact from '../../assets/ethereum/ABI/token/ERC721Token.sol/ERC721Token.json';
import ERC1155Artifact from '../../assets/ethereum/ABI/token/ERC1155Token.sol/ERC1155Token.json';

import { AddressBook, ContractName } from './types';
import { Contract, ethers, providers, Signer } from 'ethers';
import { JsonFragment } from '@ethersproject/abi';
import { ErrorDescription } from '@ethersproject/abi/lib/interface';

export const ERC20Abi = ERC20Artifact.abi;
export const ERC721Abi = ERC721Artifact.abi;
export const ERC1155Abi = ERC1155Artifact.abi;

// -----------------------------------------------------------------------------
// Address book
// -----------------------------------------------------------------------------

// Make sure ContractName in ./types includes all of these keys.
export const ADDRESSES: AddressBook = {
    /**
     * MAINNET LATEST
     * 1/23/26
     */
    LiqEthAuthority: "0xB8fb7dBA2de501c94EAB723fa48679f698a39Aa4",
    BeaconState: "0x29719E8e76C52F5fD4888DF401579859c305f2DF",
    WithdrawalQueue: "0xce388339bFDAed9cAF536ea7b4F42123b4D755d4",
    LiqEthToken: "0x2C0e75e930Fc72ceD0725854C5E7A55566333F3f",
    Accounting: "0xE31C44d04c7B055afAC9301F55840f030eaF04BC",
    DepositManager: "0x21b339ED55A0f037f91B88044C808CcfCCBbF9A8",
    WithdrawalVault: "0x028743bE7E727aA331A1e952575cb00623B95fE2",
    StakingModule: "0xDe9754Fb58CcD4D74e94DA5682192E4e6eC334b0",
    YieldOracle: "0x7B7B8894e75b44B6cA7Dc8bE6dED5059621907b2",

     OutpostManagerAuthority: "0xeaE32b94c0F858b3600861aAE4Caeee6B6341751",
    iodata: "0x485Ed153c45189d623Bb37816eD3016A56552273",
    Base58: "0x8bB3856EC1114B1cE45a78620CF174D90a1Fd4e7",
    sysio_merkle: "0x5180a926D71c6d94c5c939Fe7Fed14cD7dcAa541",
    ReceiptNFT: "0x651De616A20b17De1095f9A0694A0e0E68025b78",
    EthUsdPriceConsumer: "0x2d866f8DB6F6c94997b0027Ba24bc64a03627ddc",
    Pool: "0xDA268Adcb90c422C2102A5f713182A42A8b9EA0D",
    OutpostManager: "0x9f279804Cb8539c53709AA37a4C1b30A52A85175",
    sysio_write: "0x3F8c880efd2c8C3aA1f0CE40b886cc6913eEC1d3",
    Pretoken: "0xCabF19Fdb7cD063738dd63a418702Ee3642FFc68",
    BAR: "0x2D8f4D4435509d7b8F93C3Ba93D45447C892f7ED",
    OPPCommon: "0x1716EEC9f1E0c1aF7BbbA8Fed10652138914f766",
    OPP: "0x5e1135cF8987268a3e7F68551558Ae92c454429f",
    Depositor: "0xf4b4ccfD684feB46504D77232FBde23b23d4F774",
    OPPInbound: "0x088484d45fF93AFabC63810FB0b335792E2B24f3"

    /**
     * HOODI TESTNET LATEST
     * 1/23/26
     */
    // LiqETH contracts
    // LiqEthAuthority: "0x3Eb66Bb91F7a022FbABc1619Cb6acce7025Bc41F",
    // BeaconState: "0x08F879c6D0964E0A17bF688Af23168f654DF372e",
    // WithdrawalQueue: "0x74aDeD940369baDDb0F36426F23B08499e45Fe18",
    // LiqEthToken: "0x8C974517D038e2e3b8a1da7b1A026bC8132A4b2e",
    // Accounting: "0x766CE996B8132b927988c5b2ECAE8108fB1a9307",
    // DepositManager: "0xEd391dB5DB11B5eDBE08e69A51D16e84293C09a3",
    // WithdrawalVault: "0xbaBc0D5997283ab9886696A0dEaB8A8093436b48",
    // StakingModule: "0x4998fBb6Ca5Ee3Db6c339e2962A34791Af643fEa",
    // YieldOracle: "0x61Fe311C57dd9a38Fb7B0c5A8ec521452dB25378",
    // //Outpost contracts
    // OutpostManagerAuthority: "0x3B8705Ba1195AF632b3bd9E46F19348BC828FA3D",
    // iodata: "0x4aCABbC74a1cA9B0058d9ADAa0E00Af0f4f73212",
    // Base58: "0x1241F36ff30065deD52d51f0d6524EF971259A12",
    // sysio_merkle: "0x58ca9b5515266Fd6bF94D5F1268C505b2eC9679c",
    // ReceiptNFT: "0x03d6bD46cA9F54B8F8bbA21CfAfe717b36cB8593",
    // EthUsdPriceConsumer: "0xdC2DD993A13b3DD6D255B52285625694bd967c9A",
    // Pool: "0xd4742757AA61B1Fd3Fcd87980D7D460B12Cd9275",
    // OutpostManager: "0x38ec2B56cBCeCaFFe28b535517de1a102C3D74E3",
    // sysio_write: "0x0Ec4C1BaB772958aa3Db27459643bfbE7d92E305",
    // Pretoken: "0x8c97a51A71640fa411DEfab8feC3401eFfd1a2E4",
    // BAR: "0xbAFac13ffd3Aa9aEabaaeb6e4C2cF3dFf3479DC0",
    // OPPCommon: "0x12283a6d4E6cE4A0e8AdB319Fc161c7F4f91EA8F",
    // OPP: "0x87CE3Fe2CDca39C04aa2c14bbF1E1d9DB6358C4A",
    // Depositor: "0x5351B30EA4b75bD9be9Fc0048e88F675f7b3Ac2b",
    // OPPInbound: "0x0B1f7FB1c7f01A9cf490a1387E1E100F2bEA6C6d",
    // MockAggregator: "0xFCfc3ddd4CBd9Ad3b3af3A374B8bdA1b66eE6FFF",
};

export type Contracts<T extends string = ContractName> = Record<T, ContractConfig>;

export type ContractConfig = {
    address: string;
    abi: JsonFragment[];
};

// -----------------------------------------------------------------------------
// Contract config
// -----------------------------------------------------------------------------

export const CONTRACTS: Contracts<ContractName> = {
    // LiqETH Contracts
    LiqEthAuthority: {
        address: ADDRESSES.LiqEthAuthority,
        abi: LiqEthArtifact.abi as JsonFragment[],
    },
    BeaconState: {
        address: ADDRESSES.BeaconState,
        abi: BeaconStateArtifact.abi as JsonFragment[]
    },
    WithdrawalQueue: {
        address: ADDRESSES.WithdrawalQueue,
        abi: WithdrawalQueueArtifact.abi as JsonFragment[],
    },
    LiqEthToken: {
        address: ADDRESSES.LiqEthToken,
        abi: LiqEthArtifact.abi as JsonFragment[],
    },
    Accounting: {
        address: ADDRESSES.Accounting,
        abi: AccountingArtifact.abi as JsonFragment[],
    },
    DepositManager: {
        address: ADDRESSES.DepositManager,
        abi: DepositManagerArtifact.abi as JsonFragment[],
    },
    WithdrawalVault: {
        address: ADDRESSES.WithdrawalVault,
        abi: WithdrawalVaultArtifact.abi as JsonFragment[],
    },
    StakingModule: {
        address: ADDRESSES.StakingModule,
        abi: StakingModuleArtifact.abi as JsonFragment[],
    },
    YieldOracle: {
        address: ADDRESSES.YieldOracle,
        abi: YieldOracleArtifact.abi as JsonFragment[],
    },

    // Outpost Contracts
    OutpostManagerAuthority: {
        address: ADDRESSES.OutpostManagerAuthority,
        abi: OutpostManagerAuthorityArtifact.abi as JsonFragment[],
    },
    iodata: {
        address: ADDRESSES.iodata,
        abi: IodataArtifact.abi as JsonFragment[],
    },
    Base58: {
        address: ADDRESSES.Base58,
        abi: Base58Artifact.abi as JsonFragment[],
    },
    sysio_merkle: {
        address: ADDRESSES.sysio_merkle,
        abi: sysio_merkleArtifact.abi as JsonFragment[],
    },
    ReceiptNFT: {
        address: ADDRESSES.ReceiptNFT,
        abi: ReceiptNFTArtifact.abi as JsonFragment[],
    },
    Pool: {
        address: ADDRESSES.Pool,
        abi: PoolArtifact.abi as JsonFragment[],
    },
    OutpostManager: {
        address: ADDRESSES.OutpostManager,
        abi: OutpostManagerArtifact.abi as JsonFragment[],
    },
    sysio_write: {
        address: ADDRESSES.sysio_write,
        abi: sysio_writeArtifact.abi as JsonFragment[],
    },
    EthUsdPriceConsumer: {
        address: ADDRESSES.EthUsdPriceConsumer,
        abi: EthUsdPriceConsumerArtifact.abi as JsonFragment[],
    },
    BAR: {
        address: ADDRESSES.BAR,
        abi: BARArtifact.abi as JsonFragment[],
    },
    OPPCommon: {
        address: ADDRESSES.OPPCommon,
        abi: OPPCommonArtifact.abi as JsonFragment[],
    },
    OPP: {
        address: ADDRESSES.OPP,
        abi: OPPArtifact.abi as JsonFragment[],
    },
    Pretoken: {
        address: ADDRESSES.Pretoken,
        abi: PretokenArtifact.abi as JsonFragment[],
    },
    OPPInbound: {
        address: ADDRESSES.OPPInbound,
        abi: OPPInboundArtifact.abi as JsonFragment[],
    },
    Depositor: {
        address: ADDRESSES.Depositor,
        abi: DepositorArtifact.abi as JsonFragment[],
    },

    // MockAggregator: {
    //     address: ADDRESSES.MockAggregator,
    //     abi: AggregatorArtifact.abi as JsonFragment[],
    // },
};


export interface ContractOptions {
    /** RPC endpoint or injected EIP-1193 provider */
    provider?: ethers.providers.Provider;
    /** Only required if you need to send txns */
    signer?: Signer;
}

export class EthereumContractService {
    private provider?: providers.Provider;
    private signer?: Signer;

    public readonly contract: { [N in ContractName]: ethers.Contract } = {} as any;
    public readonly iface: { [N in ContractName]: ethers.utils.Interface } = {} as any;

    constructor(opts: ContractOptions) {
        this.provider = opts.provider;
        this.signer = opts.signer;

        (Object.keys(CONTRACTS) as ContractName[]).forEach((name) => {
            const { address, abi } = CONTRACTS[name];
            this.iface[name] = new ethers.utils.Interface(abi);
            this.contract[name] = new ethers.Contract(
                address,
                abi,
                this.signer ?? this.provider
            );
        });
    }

    /** Get raw address for a contract */
    public getAddress(name: ContractName): string {
        return CONTRACTS[name].address;
    }

    /** Utility: get a read-only handle even if signer is present */
    public getReadOnly(name: ContractName): Contract {
        const { address, abi } = CONTRACTS[name];
        return new Contract(address, abi, this.provider);
    }

    /** Utility: get a write-enabled handle (throws if no signer) */
    public getWrite(name: ContractName): Contract {
        if (!this.signer) throw new Error('No signer available for write calls');
        const { address, abi } = CONTRACTS[name];
        return new Contract(address, abi, this.signer);
    }

    /** Expose network info */
    async getNetwork() {
        return this.provider?.getNetwork();
    }

    /** List all known contract names */
    public listContractNames(): ContractName[] {
        return Object.keys(CONTRACTS) as ContractName[];
    }

    /** Get Interface instance for a contract */
    public getInterface(name: ContractName): ethers.utils.Interface {
        const iface = this.iface[name];
        if (!iface) throw new Error(`Interface not initialized for ${name}`);
        return iface;
    }

    /** Get live Contract instance */
    public getContract(name: ContractName): ethers.Contract {
        const ctr = this.contract[name];
        if (!ctr) throw new Error(`Contract not initialized for ${name}`);
        return ctr;
    }

    /** A unified Interface containing all ABIs to parse custom errors or events. */
    public get omniInterface(): ethers.utils.Interface {
        const all: any[] = [];

        const allArtifacts = [
            ERC20Artifact,
            ERC721Artifact,
            ERC1155Artifact,

            // // liqETH suite
            // AccountingArtifact,
            // DepositManagerArtifact,
            // LiqEthArtifact,
            // // LiqEthBurnArtifact,
            // // LiqEthMintArtifact,
            // // RewardsERC20Artifact,
            // StakingModuleArtifact,
            // // ValidatorBalanceArtifact,
            // WithdrawalQueueArtifact,
            // WithdrawalVaultArtifact,
            // YieldArtifact,
        ];

        allArtifacts.forEach(artifact => {
            if (artifact && artifact.abi) {
                all.push(...artifact.abi);
            }
        });

        const seen = new Set<string>();
        const dedup: any[] = [];
        for (const item of all) {
            try {
                const frag = ethers.utils.Fragment.from(item);
                const key = `${frag.type}:${frag.format(ethers.utils.FormatTypes.full)}`.toLowerCase();
                if (!seen.has(key)) {
                    seen.add(key);
                    dedup.push(item);
                }
            } catch {
                // ignore bad fragments
            }
        }

        return new ethers.utils.Interface(dedup);
    }

    /** Decode a revert or custom error payload */
    public parseError(revertData: string): ErrorDescription {
        try {
            return this.omniInterface.parseError(revertData);
        } catch {
            throw new Error(`Unable to parse error data: ${revertData}`);
        }
    }
}