import { ethers as _ethers } from 'ethers';
import { schema, TokenList, TokenInfo } from '@uniswap/token-lists';
import { TokenList as TokenList2 } from './types';
import { ajv } from './ajv';

export const ethers = (globalThis as { ethers?: typeof _ethers })?.ethers || _ethers;

export const tokenListValidator = ajv.compile<TokenList>(schema);

export const tokenValidator = (() => {
    const tokenSchema = JSON.parse(JSON.stringify(schema));

    delete tokenSchema.type;
    delete tokenSchema.properties;
    delete tokenSchema.required;

    tokenSchema['$id'] = 'Uniswap Token Schema';
    tokenSchema['$ref'] = '#/definitions/TokenInfo';

    return ajv.compile<TokenInfo>(tokenSchema);
})();

// Rebuild tokenlist in case if remote list is incorrect ( in case of coingecko )
export async function rebuildTokenList(url: string | undefined, tokenList: TokenList) {
    const newList = JSON.parse(JSON.stringify(tokenList));

    if (newList.tokens.length > 1) {
        newList.tokens.length = 1;
    }

    if (!tokenListValidator(newList)) {
        throw new Error(`${url} validation failed: ${JSON.stringify(tokenListValidator.errors, null, 2)}`);
    }

    (newList as TokenList2).tokens = tokenList.tokens
        // Filter out invalid token objects (sometimes happen with coingecko lists)
        .filter((token) => {
            return tokenValidator(token);
        })
        // Format token addresses to checksummed format
        .map((token) => {
            (token as { address: string }).address = ethers.getAddress(token.address);
            return token;
        });

    // Double check list
    if (!tokenListValidator(newList)) {
        throw new Error(`${url} validation failed: ${JSON.stringify(tokenListValidator.errors, null, 2)}`);
    }

    return newList as unknown as TokenList;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function getTokenList(url: string, fetchParams?: any): Promise<TokenList> {
    const resp = await fetch(url, {
        ...(fetchParams || {}),
        method: 'GET',
        signal: fetchParams?.signal || AbortSignal.timeout(60 * 1000),
    });

    if (!resp.ok) {
        throw new Error(resp.statusText);
    }

    const list = (await resp.json()) as TokenList;

    if (fetchParams?.rebuildList) {
        return rebuildTokenList(url, list);
    }

    if (!tokenListValidator(list)) {
        throw new Error(`${url} validation failed: ${JSON.stringify(tokenListValidator.errors, null, 2)}`);
    }

    return list;
}
