/**
 * @file policy-engine.ts
 * @notice Policy engine for route selection, compliance, and identity gating
 */

import { DestinationType } from './workflow-engine';

export interface PolicyRule {
    id: string;
    name: string;
    type: 'allowlist' | 'blocklist' | 'limit' | 'compliance' | 'identity';
    conditions: PolicyCondition[];
    action: 'allow' | 'deny' | 'review';
    priority: number;
}

export interface PolicyCondition {
    field: string;
    operator: 'eq' | 'ne' | 'gt' | 'lt' | 'gte' | 'lte' | 'in' | 'contains';
    value: any;
}

export interface IdentityCredential {
    did: string;
    vcType: string;
    issuer: string;
    claims: Record<string, any>;
    expiration?: number;
}

export interface PolicyContext {
    depositor: string;
    token: string;
    amount: string;
    destinationType: DestinationType;
    destinationChainId: number;
    identity?: IdentityCredential;
    previousTransfers?: number;
    dailyVolume?: string;
}

export interface PolicyResult {
    allowed: boolean;
    reason?: string;
    requiresReview: boolean;
    appliedRules: string[];
}

export class PolicyEngine {
    private rules: PolicyRule[] = [];
    private identityRegistry: Map<string, IdentityCredential> = new Map();

    constructor() {
        this.initializeDefaultRules();
    }

    /**
     * Initialize default policy rules
     */
    private initializeDefaultRules(): void {
        // Default: Allow all EVM routes for public access
        this.rules.push({
            id: 'default-evm-allow',
            name: 'Default EVM Allow',
            type: 'allowlist',
            conditions: [
                { field: 'destinationType', operator: 'eq', value: DestinationType.EVM }
            ],
            action: 'allow',
            priority: 100
        });

        // Default: XRPL requires Tier 1+ identity
        this.rules.push({
            id: 'xrpl-tier-requirement',
            name: 'XRPL Tier Requirement',
            type: 'identity',
            conditions: [
                { field: 'destinationType', operator: 'eq', value: DestinationType.XRPL },
                { field: 'identity.vcType', operator: 'eq', value: 'KYCTier' },
                { field: 'identity.claims.tier', operator: 'gte', value: 1 }
            ],
            action: 'allow',
            priority: 200
        });

        // Default: Fabric requires Tier 2+ identity
        this.rules.push({
            id: 'fabric-tier-requirement',
            name: 'Fabric Tier Requirement',
            type: 'identity',
            conditions: [
                { field: 'destinationType', operator: 'eq', value: DestinationType.FABRIC },
                { field: 'identity.vcType', operator: 'eq', value: 'KYCTier' },
                { field: 'identity.claims.tier', operator: 'gte', value: 2 }
            ],
            action: 'allow',
            priority: 200
        });

        // Default: Daily volume limit
        this.rules.push({
            id: 'daily-volume-limit',
            name: 'Daily Volume Limit',
            type: 'limit',
            conditions: [
                { field: 'dailyVolume', operator: 'gt', value: '1000000000000000000000' } // 1000 ETH
            ],
            action: 'review',
            priority: 150
        });
    }

    /**
     * Evaluate policy for a transfer request
     */
    async evaluatePolicy(context: PolicyContext): Promise<PolicyResult> {
        // Sort rules by priority (higher first)
        const sortedRules = [...this.rules].sort((a, b) => b.priority - a.priority);

        const appliedRules: string[] = [];
        let finalResult: PolicyResult = {
            allowed: false,
            requiresReview: false,
            appliedRules: []
        };

        for (const rule of sortedRules) {
            if (this.matchesRule(rule, context)) {
                appliedRules.push(rule.id);

                if (rule.action === 'deny') {
                    return {
                        allowed: false,
                        reason: `Blocked by rule: ${rule.name}`,
                        requiresReview: false,
                        appliedRules
                    };
                } else if (rule.action === 'review') {
                    finalResult = {
                        allowed: false,
                        reason: `Requires review: ${rule.name}`,
                        requiresReview: true,
                        appliedRules
                    };
                } else if (rule.action === 'allow') {
                    finalResult = {
                        allowed: true,
                        requiresReview: false,
                        appliedRules
                    };
                    // Continue to check for higher priority deny rules
                }
            }
        }

        // If no rules matched, default deny
        if (appliedRules.length === 0) {
            return {
                allowed: false,
                reason: 'No matching policy rules',
                requiresReview: false,
                appliedRules: []
            };
        }

        return finalResult;
    }

    /**
     * Check if context matches rule conditions
     */
    private matchesRule(rule: PolicyRule, context: PolicyContext): boolean {
        return rule.conditions.every(condition => {
            const fieldValue = this.getFieldValue(context, condition.field);
            return this.evaluateCondition(fieldValue, condition.operator, condition.value);
        });
    }

    /**
     * Get field value from context
     */
    private getFieldValue(context: PolicyContext, field: string): any {
        const parts = field.split('.');
        let value: any = context;

        for (const part of parts) {
            if (value && typeof value === 'object' && part in value) {
                value = value[part as keyof typeof value];
            } else {
                return undefined;
            }
        }

        return value;
    }

    /**
     * Evaluate a single condition
     */
    private evaluateCondition(
        fieldValue: any,
        operator: string,
        expectedValue: any
    ): boolean {
        switch (operator) {
            case 'eq':
                return fieldValue === expectedValue;
            case 'ne':
                return fieldValue !== expectedValue;
            case 'gt':
                return Number(fieldValue) > Number(expectedValue);
            case 'lt':
                return Number(fieldValue) < Number(expectedValue);
            case 'gte':
                return Number(fieldValue) >= Number(expectedValue);
            case 'lte':
                return Number(fieldValue) <= Number(expectedValue);
            case 'in':
                return Array.isArray(expectedValue) && expectedValue.includes(fieldValue);
            case 'contains':
                return String(fieldValue).includes(String(expectedValue));
            default:
                return false;
        }
    }

    /**
     * Add a policy rule
     */
    addRule(rule: PolicyRule): void {
        this.rules.push(rule);
        this.rules.sort((a, b) => b.priority - a.priority);
    }

    /**
     * Remove a policy rule
     */
    removeRule(ruleId: string): boolean {
        const index = this.rules.findIndex(r => r.id === ruleId);
        if (index >= 0) {
            this.rules.splice(index, 1);
            return true;
        }
        return false;
    }

    /**
     * Register identity credential
     */
    registerIdentity(address: string, credential: IdentityCredential): void {
        this.identityRegistry.set(address, credential);
    }

    /**
     * Get identity credential
     */
    getIdentity(address: string): IdentityCredential | undefined {
        return this.identityRegistry.get(address);
    }

    /**
     * Get all rules
     */
    getAllRules(): PolicyRule[] {
        return [...this.rules];
    }
}
