/**
 * @file workflow-engine.ts
 * @notice FireFly workflow engine for bridge transfer orchestration
 */

import { ethers } from 'ethers';
import { BridgeEscrowVault, BridgeRegistry } from '../../contracts/bridge/interop';

export enum TransferStatus {
    INITIATED = 'INITIATED',
    DEPOSIT_CONFIRMED = 'DEPOSIT_CONFIRMED',
    ROUTE_SELECTED = 'ROUTE_SELECTED',
    EXECUTING = 'EXECUTING',
    DESTINATION_SENT = 'DESTINATION_SENT',
    FINALITY_CONFIRMED = 'FINALITY_CONFIRMED',
    COMPLETED = 'COMPLETED',
    FAILED = 'FAILED',
    REFUND_PENDING = 'REFUND_PENDING',
    REFUNDED = 'REFUNDED'
}

export enum DestinationType {
    EVM = 0,
    XRPL = 1,
    FABRIC = 2
}

export interface Transfer {
    transferId: string;
    depositor: string;
    asset: string;
    amount: string;
    destinationType: DestinationType;
    destinationData: string;
    timestamp: number;
    timeout: number;
    status: TransferStatus;
    refunded: boolean;
    route?: RouteInfo;
    executionData?: ExecutionData;
}

export interface RouteInfo {
    chainId: number;
    chainName: string;
    provider: string;
    estimatedTime: number;
    fee: string;
    healthScore: number;
}

export interface ExecutionData {
    txHash?: string;
    blockNumber?: number;
    finalityBlocks?: number;
    xrplTxHash?: string;
    fabricTxId?: string;
    error?: string;
}

export class WorkflowEngine {
    private provider: ethers.Provider;
    private escrowVault: ethers.Contract;
    private registry: ethers.Contract;
    private stateMachine: Map<string, TransferStatus>;

    constructor(
        rpcUrl: string,
        escrowVaultAddress: string,
        registryAddress: string,
        escrowAbi: any[],
        registryAbi: any[]
    ) {
        this.provider = new ethers.JsonRpcProvider(rpcUrl);
        this.escrowVault = new ethers.Contract(escrowVaultAddress, escrowAbi, this.provider);
        this.registry = new ethers.Contract(registryAddress, registryAbi, this.provider);
        this.stateMachine = new Map();
    }

    /**
     * Initialize a new transfer workflow
     */
    async initiateTransfer(
        transferId: string,
        depositor: string,
        asset: string,
        amount: string,
        destinationType: DestinationType,
        destinationData: string,
        timeout: number
    ): Promise<Transfer> {
        const transfer: Transfer = {
            transferId,
            depositor,
            asset,
            amount,
            destinationType,
            destinationData,
            timestamp: Date.now(),
            timeout,
            status: TransferStatus.INITIATED,
            refunded: false
        };

        this.stateMachine.set(transferId, TransferStatus.INITIATED);
        return transfer;
    }

    /**
     * Confirm deposit on-chain
     */
    async confirmDeposit(transferId: string): Promise<void> {
        const currentStatus = this.stateMachine.get(transferId);
        if (currentStatus !== TransferStatus.INITIATED) {
            throw new Error(`Invalid status transition from ${currentStatus}`);
        }

        // Verify deposit on-chain
        const transfer = await this.escrowVault.getTransfer(transferId);
        if (!transfer || transfer.status !== 0) { // 0 = INITIATED
            throw new Error('Deposit not found or invalid status');
        }

        this.stateMachine.set(transferId, TransferStatus.DEPOSIT_CONFIRMED);
    }

    /**
     * Select route for transfer
     */
    async selectRoute(transferId: string, route: RouteInfo): Promise<void> {
        const currentStatus = this.stateMachine.get(transferId);
        if (currentStatus !== TransferStatus.DEPOSIT_CONFIRMED) {
            throw new Error(`Invalid status transition from ${currentStatus}`);
        }

        // Validate route with registry
        const isValid = await this.registry.validateBridgeRequest(
            route.chainId === 0 ? ethers.ZeroAddress : route.chainId,
            route.chainId,
            route.chainId
        );

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

        this.stateMachine.set(transferId, TransferStatus.ROUTE_SELECTED);
    }

    /**
     * Execute transfer
     */
    async executeTransfer(transferId: string, executionData: ExecutionData): Promise<void> {
        const currentStatus = this.stateMachine.get(transferId);
        if (currentStatus !== TransferStatus.ROUTE_SELECTED) {
            throw new Error(`Invalid status transition from ${currentStatus}`);
        }

        this.stateMachine.set(transferId, TransferStatus.EXECUTING);
        // Execution logic handled by connector layer
    }

    /**
     * Mark destination as sent
     */
    async markDestinationSent(transferId: string, executionData: ExecutionData): Promise<void> {
        const currentStatus = this.stateMachine.get(transferId);
        if (currentStatus !== TransferStatus.EXECUTING) {
            throw new Error(`Invalid status transition from ${currentStatus}`);
        }

        this.stateMachine.set(transferId, TransferStatus.DESTINATION_SENT);
    }

    /**
     * Confirm finality
     */
    async confirmFinality(transferId: string): Promise<void> {
        const currentStatus = this.stateMachine.get(transferId);
        if (currentStatus !== TransferStatus.DESTINATION_SENT) {
            throw new Error(`Invalid status transition from ${currentStatus}`);
        }

        this.stateMachine.set(transferId, TransferStatus.FINALITY_CONFIRMED);
    }

    /**
     * Complete transfer
     */
    async completeTransfer(transferId: string): Promise<void> {
        const currentStatus = this.stateMachine.get(transferId);
        if (currentStatus !== TransferStatus.FINALITY_CONFIRMED) {
            throw new Error(`Invalid status transition from ${currentStatus}`);
        }

        this.stateMachine.set(transferId, TransferStatus.COMPLETED);
    }

    /**
     * Mark transfer as failed
     */
    async failTransfer(transferId: string, error: string): Promise<void> {
        this.stateMachine.set(transferId, TransferStatus.FAILED);
    }

    /**
     * Initiate refund
     */
    async initiateRefund(transferId: string): Promise<void> {
        const currentStatus = this.stateMachine.get(transferId);
        if (currentStatus === TransferStatus.FAILED || 
            currentStatus === TransferStatus.INITIATED ||
            currentStatus === TransferStatus.DEPOSIT_CONFIRMED) {
            this.stateMachine.set(transferId, TransferStatus.REFUND_PENDING);
        } else {
            throw new Error(`Cannot refund from status ${currentStatus}`);
        }
    }

    /**
     * Execute refund
     */
    async executeRefund(transferId: string): Promise<void> {
        const currentStatus = this.stateMachine.get(transferId);
        if (currentStatus !== TransferStatus.REFUND_PENDING) {
            throw new Error(`Invalid status transition from ${currentStatus}`);
        }

        this.stateMachine.set(transferId, TransferStatus.REFUNDED);
    }

    /**
     * Get current status
     */
    getStatus(transferId: string): TransferStatus | undefined {
        return this.stateMachine.get(transferId);
    }

    /**
     * Check if transfer is refundable
     */
    async isRefundable(transferId: string): Promise<boolean> {
        return await this.escrowVault.isRefundable(transferId);
    }
}
