import { _sendRequest } from "../utils/httpClients";
import { Transaction } from "../types/transaction";
import { ScoringAnalysis, ScoringAnalysisType, ScoringObjectType } from "../types/scoringAnalysis";
import { FeedWithdrawal } from "../types/feedWithdrawal";
import { Address } from "../types/address";
import { BlockchainEnum } from "../types/common";
import fs from "fs";
import path from "path";

export class ScorechainConnector {
    private readonly _apiUrl: string;
    private readonly _header: { "x-api-key": string };
    private readonly shouldVerifyAuthenticity: boolean;

    constructor(apiKey: string, shouldVerifyAuthenticity = true) {
        this._apiUrl = "https://api.scorechain.com/v1/";
        this._header = {
            "x-api-key": apiKey,
        };
        this.shouldVerifyAuthenticity = shouldVerifyAuthenticity;
    }

    /**
     * Retrieve and update local non deprecated public key
     * @returns A promise that resolves to the retrieved public keys.
     */
    async updateScorechainPublicKey() {
        const publicKeys = await this.getRequest<{ key: string; isDeprecated: boolean }[]>(`${this._apiUrl}publicKeys`);
        const validPublicKey = publicKeys.find(publicKey => !publicKey.isDeprecated);
        fs.writeFileSync(path.join(__dirname, "../../scorechainPublicKey", "public.pem"), validPublicKey?.key as string);
    }

    /**
     * Retrieve a transaction by its hash from the specified blockchain.
     * @param transactionHash - The hash of the transaction to retrieve.
     * @param blockchain - The blockchain from which to retrieve the transaction.
     * @returns A promise that resolves to the retrieved transaction.
     */
    async getTransaction(transactionHash: string, blockchain: BlockchainEnum): Promise<Transaction> {
        return this.getRequest<Transaction>(`${this._apiUrl}/blockchains/${blockchain}/transactions/${transactionHash}`);
    }

    /**
     * Retrieve information about a specific address from the blockchain.
     * @param address - The address to retrieve information for.
     * @param blockchain - The blockchain to query.
     * @returns A Promise that resolves to an Address object containing information about the address.
     */
    async getAddress(address: string, blockchain: BlockchainEnum): Promise<Address> {
        return this.getRequest<Address>(`${this._apiUrl}/blockchains/${blockchain}/addresses/${address}`);
    }

    private _scoringAnalysis(
        analysisType: ScoringAnalysisType,
        objectType: ScoringObjectType,
        blockchain: BlockchainEnum,
        objectId: string,
        coin: string,
        depth?: number
    ) {
        return this.postRequest<ScoringAnalysis>(`${this._apiUrl}/scoringAnalysis`, {
            analysisType,
            objectType,
            objectId,
            blockchain,
            coin,
            depth,
        });
    }

    /**
     * Get a real-time scoring analysis for a transaction.
     * @param transactionHash - The hash of the transaction.
     * @param blockchain - The blockchain on which the transaction occurred.
     * @param coin - The coin associated with the transaction.
     *  - ALL: get a global analysis (this is limited to addresses of less than 10K transactions and will return a 422 error otherwise).
     *  - MAIN: get an analysis on the main coin of the specified blockchain
     *  - [coinChainId]: get an analysis for a specific coin by providing the hash of the token contract.
     * @param direction - The direction of the scoring analysis.
     * @param depth - The depth of the scoring analysis (optional). For UTXO-based blockchains, default and maximum is enforced at 100. For account-based blockchains, default and maximum is enforced at 6.
     * @returns A promise that resolves to the scoring analysis.
     */
    async getTransactionScoringAnalysis(
        transactionHash: string,
        blockchain: BlockchainEnum,
        coin: "MAIN" | "ALL" | string,
        direction: ScoringAnalysisType,
        depth?: number
    ) {
        return this._scoringAnalysis(direction, ScoringObjectType.TRANSACTION, blockchain, transactionHash, coin, depth);
    }

    /**
     * Get a real-time scoring analysis for a specific address.
     * @param addressHash - The hash of the address to retrieve scoring analysis for.
     * @param blockchain - The blockchain to query for scoring analysis.
     * @param coin - The coin associated with the address.
     *  - ALL: get a global analysis (this is limited to addresses of less than 10K transactions and will return a 422 error otherwise).
     *  - MAIN: get an analysis on the main coin of the specified blockchain
     *  - [coinChainId]: get an analysis for a specific coin by providing the hash of the token contract.
     * @param direction - The direction of the scoring analysis.
     * @param depth - The depth of the scoring analysis (optional). For UTXO-based blockchains, default and maximum is enforced at 100. For account-based blockchains, default and maximum is enforced at 6.
     * @returns A promise that resolves to the scoring analysis for the address.
     */
    async getAddressScoringAnalysis(
        addressHash: string,
        blockchain: BlockchainEnum,
        coin: "MAIN" | "ALL" | string,
        direction: ScoringAnalysisType,
        depth?: number
    ) {
        return this._scoringAnalysis(direction, ScoringObjectType.ADDRESS, blockchain, addressHash, coin, depth);
    }

    /**
     * Get a real-time scoring analysis for a wallet.
     * @param addressHash - The hash of the wallet address.
     * @param blockchain - The blockchain to retrieve the scoring analysis from.
     * @param coin - The coin type for the wallet.
     *  - ALL: get a global analysis (this is limited to addresses of less than 10K transactions and will return a 422 error otherwise).
     *  - MAIN: get an analysis on the main coin of the specified blockchain
     *  - [coinChainId]: get an analysis for a specific coin by providing the hash of the token contract.
     * @param direction - The direction of the scoring analysis.
     * @param depth - The depth of the scoring analysis (optional). For UTXO-based blockchains, default and maximum is enforced at 100. For account-based blockchains, default and maximum is enforced at 6.
     * @returns A promise that resolves to the scoring analysis for the wallet.
     */
    async getWalletScoringAnalysis(
        addressHash: string,
        blockchain: BlockchainEnum,
        coin: "MAIN" | "ALL" | string,
        direction: ScoringAnalysisType,
        depth?: number
    ) {
        return this._scoringAnalysis(direction, ScoringObjectType.WALLET, blockchain, addressHash, coin, depth);
    }

    /**
     * Feed a transaction for analysis by risk scenarios.
     * @param transactionHash - The hash of the transaction to be scored.
     * @param blockchain - The blockchain on which the transaction occurred.
     * @param direction - The direction of the transaction (incoming or outgoing).
     * @param address - The address associated with the transaction.
     * @param customerRefId - (Optional) Identifier used to determine if multiple transactions should be considered as referring to the same customer. If not provided, the blockchain address is considered the identifier.
     * @returns A promise that resolves to void.
     */
    async feedTransaction(
        transactionHash: string,
        blockchain: BlockchainEnum,
        direction: ScoringObjectType,
        address: string,
        customerRefId?: string
    ) {
        return this.postRequest<void>(`${this._apiUrl}/feedTransaction`, {
            blockchain,
            hash: transactionHash,
            direction,
            address,
            customerRefId,
        });
    }

    /**
     * Feed a not approved withdrawal for analysis by risk scenarios.
     * @param address - The address to send the withdrawal to.
     * @param blockchain - The blockchain to use for the withdrawal.
     * @param amount - The amount to withdraw.
     * @param coinChainId - hash of the token address or 'MAIN' for the base asset
     * @param identifier - (Optional) A unique UUID that serves as the identifier for a specific withdrawal operation.
     * @returns A promise that resolves to the result of the withdrawal feed.
     */
    async feedWithdrawal(
        address: string,
        blockchain: BlockchainEnum,
        amount: number,
        coinChainId: "MAIN" | string,
        identifier?: string
    ) {
        return this.postRequest<FeedWithdrawal>(`${this._apiUrl}/feedWithdrawal`, {
            blockchain,
            address,
            amount,
            coinChainId,
            identifier,
        });
    }

    private getRequest<T>(url: string): Promise<T> {
        return _sendRequest(url, "GET", {}, this._header, this.shouldVerifyAuthenticity);
    }

    private postRequest<T>(url: string, data: object): Promise<T> {
        return _sendRequest(url, "POST", data, this._header, this.shouldVerifyAuthenticity);
    }
}
