/**
 * @file quote-service.ts
 * @notice Quote service for bridge transfers with route intelligence
 */

import { ethers } from 'ethers';
import { BridgeRegistry } from '../../contracts/bridge/interop';
import { RouteInfo, DestinationType } from './workflow-engine';

export interface QuoteRequest {
    token: string; // address(0) for native
    amount: string;
    destinationChainId: number;
    destinationType: DestinationType;
    destinationAddress: string;
}

export interface QuoteResponse {
    transferId: string;
    routes: RouteInfo[];
    recommendedRoute: RouteInfo;
    totalFee: string;
    minReceived: string;
    estimatedTime: number;
    slippage: string;
    riskLevel: number;
    /** Best swap quote (e.g. Dodoex) on source chain when EnhancedSwapRouter is configured */
    sourceSwapQuote?: string;
    /** Best swap quote on destination chain when swap router is configured */
    destinationSwapQuote?: string;
}

const ENHANCED_SWAP_ROUTER_ABI = [
    'function getQuotes(address stablecoinToken, uint256 amountIn) view returns (uint8[] providers, uint256[] amounts)',
];

export class QuoteService {
    private provider: ethers.Provider;
    private registry: ethers.Contract;
    private thirdwebClientId?: string;
    private enhancedSwapRouterAddress?: string;
    private destinationProvider?: ethers.Provider;
    private destinationSwapRouterAddress?: string;

    constructor(
        rpcUrl: string,
        registryAddress: string,
        registryAbi: any[],
        thirdwebClientId?: string,
        enhancedSwapRouterAddress?: string,
        destinationRpcUrl?: string,
        destinationSwapRouterAddress?: string
    ) {
        this.provider = new ethers.JsonRpcProvider(rpcUrl);
        this.registry = new ethers.Contract(registryAddress, registryAbi, this.provider);
        this.thirdwebClientId = thirdwebClientId;
        this.enhancedSwapRouterAddress = enhancedSwapRouterAddress;
        if (destinationRpcUrl) {
            this.destinationProvider = new ethers.JsonRpcProvider(destinationRpcUrl);
        }
        this.destinationSwapRouterAddress = destinationSwapRouterAddress;
    }

    /**
     * Get quote for bridge transfer
     */
    async getQuote(request: QuoteRequest): Promise<QuoteResponse> {
        // Validate request with registry
        const [isValid, fee] = await this.registry.validateBridgeRequest(
            request.token,
            request.amount,
            request.destinationChainId
        );

        if (!isValid) {
            throw new Error('Invalid bridge request');
        }

        // Get route options
        const routes = await this.getRouteOptions(
            request.token,
            request.amount,
            request.destinationChainId,
            request.destinationType
        );

        // Select best route
        const recommendedRoute = this.selectBestRoute(routes);

        // Calculate totals
        const totalFee = BigInt(fee.toString());
        const amountBigInt = BigInt(request.amount);
        const minReceived = amountBigInt - totalFee;

        // Estimate time based on destination
        const estimatedTime = this.estimateSettlementTime(
            request.destinationType,
            request.destinationChainId
        );

        // Calculate slippage (default 0.5%)
        const slippage = '50'; // 50 basis points = 0.5%

        // Get risk level from registry
        let riskLevel = 0;
        if (request.token !== ethers.ZeroAddress) {
            const tokenConfig = await this.registry.tokenConfigs(request.token);
            riskLevel = tokenConfig.riskLevel;
        }

        // Optional: Dodoex-inclusive swap quotes when EnhancedSwapRouter is configured
        let sourceSwapQuote: string | undefined;
        let destinationSwapQuote: string | undefined;
        if (this.enhancedSwapRouterAddress && request.token !== ethers.ZeroAddress) {
            try {
                const router = new ethers.Contract(
                    this.enhancedSwapRouterAddress,
                    ENHANCED_SWAP_ROUTER_ABI,
                    this.provider
                );
                const [providers, amounts] = await router.getQuotes(request.token, request.amount);
                if (amounts && amounts.length > 0) {
                    const best = amounts.reduce((a: bigint, b: bigint) => (a > b ? a : b), 0n);
                    sourceSwapQuote = best.toString();
                }
            } catch {
                // Router not deployed or getQuotes failed; leave undefined
            }
        }
        if (this.destinationProvider && this.destinationSwapRouterAddress && request.token !== ethers.ZeroAddress) {
            try {
                const router = new ethers.Contract(
                    this.destinationSwapRouterAddress,
                    ENHANCED_SWAP_ROUTER_ABI,
                    this.destinationProvider
                );
                const [providers, amounts] = await router.getQuotes(request.token, request.amount);
                if (amounts && amounts.length > 0) {
                    const best = amounts.reduce((a: bigint, b: bigint) => (a > b ? a : b), 0n);
                    destinationSwapQuote = best.toString();
                }
            } catch {
                // Destination router not available
            }
        }

        return {
            transferId: this.generateTransferId(),
            routes,
            recommendedRoute,
            totalFee: totalFee.toString(),
            minReceived: minReceived.toString(),
            estimatedTime,
            slippage,
            riskLevel,
            sourceSwapQuote,
            destinationSwapQuote
        };
    }

    /**
     * Get available route options
     */
    private async getRouteOptions(
        token: string,
        amount: string,
        destinationChainId: number,
        destinationType: DestinationType
    ): Promise<RouteInfo[]> {
        const routes: RouteInfo[] = [];

        if (destinationType === DestinationType.EVM) {
            // Get EVM routes via thirdweb or direct bridge
            if (this.thirdwebClientId) {
                const thirdwebRoute = await this.getThirdwebRoute(
                    token,
                    amount,
                    destinationChainId
                );
                if (thirdwebRoute) {
                    routes.push(thirdwebRoute);
                }
            }

            // Add direct bridge route if available
            const directRoute = await this.getDirectBridgeRoute(
                token,
                amount,
                destinationChainId
            );
            if (directRoute) {
                routes.push(directRoute);
            }
        } else if (destinationType === DestinationType.XRPL) {
            // XRPL route via Cacti
            const xrplRoute = await this.getXRPLRoute(token, amount);
            if (xrplRoute) {
                routes.push(xrplRoute);
            }
        } else if (destinationType === DestinationType.FABRIC) {
            // Fabric route via Cacti
            const fabricRoute = await this.getFabricRoute(token, amount);
            if (fabricRoute) {
                routes.push(fabricRoute);
            }
        }

        return routes;
    }

    /**
     * Get thirdweb route quote
     */
    private async getThirdwebRoute(
        token: string,
        amount: string,
        destinationChainId: number
    ): Promise<RouteInfo | null> {
        try {
            // Call thirdweb API for quote
            const response = await fetch(
                `https://api.thirdweb.com/v1/bridge/quote?clientId=${this.thirdwebClientId}`,
                {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        fromChain: 138,
                        toChain: destinationChainId,
                        fromToken: token,
                        toToken: token, // Same token on destination
                        amount: amount
                    })
                }
            );

            if (!response.ok) return null;

            const data = await response.json();
            const healthScore = await this.registry.getRouteHealthScore(
                destinationChainId,
                token
            );

            return {
                chainId: destinationChainId,
                chainName: await this.getChainName(destinationChainId),
                provider: 'thirdweb',
                estimatedTime: data.estimatedTime || 300, // 5 minutes default
                fee: data.fee || '0',
                healthScore: Number(healthScore)
            };
        } catch (error) {
            console.error('Thirdweb quote error:', error);
            return null;
        }
    }

    /**
     * Get direct bridge route
     */
    private async getDirectBridgeRoute(
        token: string,
        amount: string,
        destinationChainId: number
    ): Promise<RouteInfo | null> {
        const destination = await this.registry.destinations(destinationChainId);
        if (!destination.enabled) return null;

        const healthScore = await this.registry.getRouteHealthScore(destinationChainId, token);
        const baseFee = (BigInt(amount) * BigInt(destination.baseFee)) / BigInt(10000);

        return {
            chainId: destinationChainId,
            chainName: destination.chainName,
            provider: 'direct',
            estimatedTime: destination.timeoutSeconds / 2, // Estimate half of timeout
            fee: baseFee.toString(),
            healthScore: Number(healthScore)
        };
    }

    /**
     * Get XRPL route
     */
    private async getXRPLRoute(token: string, amount: string): Promise<RouteInfo | null> {
        // XRPL destination has chainId = 0
        const destination = await this.registry.destinations(0);
        if (!destination.enabled) return null;

        const baseFee = (BigInt(amount) * BigInt(destination.baseFee)) / BigInt(10000);

        return {
            chainId: 0,
            chainName: 'XRPL',
            provider: 'cacti-xrpl',
            estimatedTime: 60, // XRPL is fast, ~3-5 seconds, but add buffer
            fee: baseFee.toString(),
            healthScore: 8000 // Default high score for XRPL
        };
    }

    /**
     * Get Fabric route. Set FABRIC_CHAIN_ID in env when Fabric is live; default 999 until then.
     */
    private async getFabricRoute(token: string, amount: string): Promise<RouteInfo | null> {
        const fabricChainId = process.env.FABRIC_CHAIN_ID ? parseInt(process.env.FABRIC_CHAIN_ID, 10) : 999;
        return {
            chainId: fabricChainId,
            chainName: 'Hyperledger Fabric',
            provider: 'cacti-fabric',
            estimatedTime: 120, // Fabric settlement time
            fee: '0', // May vary
            healthScore: 7000
        };
    }

    /**
     * Select best route based on health score, fee, and time
     */
    private selectBestRoute(routes: RouteInfo[]): RouteInfo {
        if (routes.length === 0) {
            throw new Error('No routes available');
        }

        // Score routes: health (40%), low fee (30%), fast time (30%)
        const scored = routes.map(route => {
            const healthScore = route.healthScore / 10000; // Normalize to 0-1
            const feeScore = 1 - (parseFloat(route.fee) / parseFloat(routes[0].amount || '1')); // Relative fee
            const timeScore = 1 - (route.estimatedTime / 3600); // Normalize to 1 hour max

            const totalScore = healthScore * 0.4 + feeScore * 0.3 + timeScore * 0.3;

            return { route, score: totalScore };
        });

        scored.sort((a, b) => b.score - a.score);
        return scored[0].route;
    }

    /**
     * Estimate settlement time
     */
    private estimateSettlementTime(
        destinationType: DestinationType,
        chainId: number
    ): number {
        if (destinationType === DestinationType.XRPL) {
            return 60; // ~1 minute for XRPL
        } else if (destinationType === DestinationType.FABRIC) {
            return 120; // ~2 minutes for Fabric
        } else {
            // EVM chains: estimate based on finality
            return 300; // Default 5 minutes for EVM
        }
    }

    /**
     * Get chain name
     */
    private async getChainName(chainId: number): Promise<string> {
        const destination = await this.registry.destinations(chainId);
        return destination.chainName || `Chain ${chainId}`;
    }

    /**
     * Generate unique transfer ID
     */
    private generateTransferId(): string {
        return ethers.keccak256(
            ethers.toUtf8Bytes(`${Date.now()}-${Math.random()}`)
        );
    }
}

/**
 * Create QuoteService from environment variables (for bridge API / orchestration).
 * Set RPC_URL, BRIDGE_REGISTRY_ADDRESS; optional: ENHANCED_SWAP_ROUTER_ADDRESS,
 * DESTINATION_RPC_URL, DESTINATION_SWAP_ROUTER_ADDRESS, THIRDWEB_CLIENT_ID.
 */
export function createQuoteServiceFromEnv(): QuoteService {
    const rpcUrl = process.env.RPC_URL || process.env.RPC_URL_138 || '';
    const registryAddress = process.env.BRIDGE_REGISTRY_ADDRESS || '';
    const registryAbi: any[] = []; // Bridge registry ABI - load from contract if needed
    if (!rpcUrl || !registryAddress) {
        throw new Error('RPC_URL (or RPC_URL_138) and BRIDGE_REGISTRY_ADDRESS are required');
    }
    return new QuoteService(
        rpcUrl,
        registryAddress,
        registryAbi,
        process.env.THIRDWEB_CLIENT_ID,
        process.env.ENHANCED_SWAP_ROUTER_ADDRESS,
        process.env.DESTINATION_RPC_URL,
        process.env.DESTINATION_SWAP_ROUTER_ADDRESS
    );
}
