import { createWallet } from './createWallet';
import cors from 'cors';
import { randomUUID } from 'crypto';
import express from 'express';
import net from 'net';
import type { Page } from 'patchright';
import { Chain, LocalAccount, Transport } from 'viem';
import { Wallet } from './createWallet';

declare global {
    interface Window {
        ethereum?: EIP1193Provider;
        eip1193Request?: (params: eip1193RequestParams) => Promise<any>;
    }
}

let wallets: Map<string, Wallet> = new Map();

export async function installMockWallet({ debug, ...params }: & { debug?: boolean; } & { page: Page } & (| {
    account: LocalAccount;
    transports?: Record<number, Transport>;
    defaultChain?: Chain;
} | { wallet: Wallet; })) {

    const freePort = await new Promise(res => {
        const srv = net.createServer();
        srv.listen(0, () => {
            const port = (srv.address() as any).port;
            srv.close((err) => res(port));
        });
    });

    const app = express();
    app.use(cors({ origin: '*', methods: [ 'POST' ], allowedHeaders: [ 'Content-Type' ] }));

    app.use(express.json());
    app.post('/eip1193', async (req: any, res: any) => {
        const { method, params, uuid, debug } = req.body;
        const wallet = wallets.get(uuid);
        if ( !wallet ) return res.status(400).json({ error: 'Wallet not found' });
        try {
            const result = await wallet.request({ method, params });
            if ( debug ) console.log('WALLET', uuid.substring(0, 8), 'REQUEST', method, params, 'RESULT', result);
            res.json(result);
        } catch ( error: any ) {
            if ( debug ) console.log('WALLET', uuid.substring(0, 8), 'REQUEST', method, params, 'ERROR', error);
            res.status(500).json({ error: error.message });
        }
    });
    app.listen(freePort);

    const browserOrPage = params.page;
    const wallet = 'wallet' in params
        ? params.wallet
        : createWallet(params.account, params.transports, params.defaultChain);

    const uuid = randomUUID();
    wallets.set(uuid, wallet);

    await browserOrPage.addInitScript(({ uuid, debug, freePort }) => {
            window.eip1193Request = async ({ method, params }) => {
                try {
                    const response = await fetch(`http://localhost:${freePort}/eip1193`, {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({ method, params, uuid, debug })
                    });
                    const result = await response.json();
                    if ( !response.ok ) throw new Error(result.error || 'Request failed');
                    return result;
                } catch ( e ) {
                    if ( debug ) console.error('EIP-1193 Error:', e);
                    throw e;
                }
            };

            function announceMockWallet() {
                const provider = {
                    request: async (request: any) => {
                        return await window.eip1193Request!({ ...request, uuid, debug });
                    },
                    on: () => {},
                    removeListener: () => {}
                };
                const info = {
                    uuid,
                    name: 'MetaMask',
                    icon: '',
                    rdns: 'io.metamask'
                };
                const detail = { info, provider };
                const announceEvent = new CustomEvent('eip6963:announceProvider', {
                    detail: Object.freeze(detail)
                });
                window.dispatchEvent(announceEvent);
                window.ethereum = provider;
            }

            announceMockWallet();
            window.addEventListener('eip6963:requestProvider', () => announceMockWallet());
            window.addEventListener('DOMContentLoaded', () => announceMockWallet());
        },
        { uuid, debug, freePort }
    );
}

interface EIP6963ProviderInfo {
    uuid: string;
    name: string;
    icon: string;
    rdns: string;
}

interface EIP1193Provider {
    request: (request: { method: string; params?: Array<unknown>; }) => Promise<unknown>; // Standard method for sending requests per EIP-1193
    on: () => void;
    removeListener: () => void;
}

export interface EIP6963ProviderDetail {
    info: EIP6963ProviderInfo;
    provider: EIP1193Provider;
}

export interface eip1193RequestParams {
    method: string;
    params?: Array<unknown>;
    uuid: string;
    debug?: boolean;
}