package org.jpos.tcpay.service;

import org.jpos.tcpay.db.entity.PosTransactionReversal;
import org.jpos.util.AppLogger;

import java.time.LocalDateTime;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Metrics and monitoring for transaction reversal system
 * Tracks reversal rates, patterns, and system health
 */
public class ReversalMetrics {
    
    private final AppLogger logger = new AppLogger();
    private final TransactionAuxUtils transactionAuxUtils;
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    
    // Alert thresholds from configuration
    private static final int MAX_PENDING_REVERSALS = 10;
    private static final int MAX_TEMP_AGE_MINUTES = 10;
    private static final double MAX_REVERSAL_RATE_THRESHOLD = 0.05; // 5% of transactions
    private static final int MONITORING_INTERVAL_SECONDS = 60;
    
    // Metrics counters
    private final AtomicInteger reversalsInitiated = new AtomicInteger(0);
    private final AtomicInteger reversalsCompleted = new AtomicInteger(0);
    private final AtomicInteger reversalsFailed = new AtomicInteger(0);
    private final AtomicInteger reversalsTimedOut = new AtomicInteger(0);
    private final AtomicLong totalReversalDuration = new AtomicLong(0);
    
    // Reason-based counters
    private final ConcurrentHashMap<String, AtomicInteger> reversalsByReason = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, AtomicInteger> reversalFailuresByReason = new ConcurrentHashMap<>();
    
    // System health metrics
    private volatile int lastPendingReversalsCount = 0;
    private volatile int lastStaleTempsCount = 0;
    private volatile double lastReversalRate = 0.0;
    private volatile LocalDateTime lastHealthCheck = LocalDateTime.now();
    
    public ReversalMetrics(TransactionAuxUtils transactionAuxUtils) {
        this.transactionAuxUtils = transactionAuxUtils;
        initializeReasonCounters();
        // Start periodic health checks
        startPeriodicHealthChecks();
    }
    
    /**
     * Initialize counters for all reversal reasons
     */
    private void initializeReasonCounters() {
        reversalsByReason.put(PosTransactionReversal.REASON_STALE_TRANSACTION, new AtomicInteger(0));
        reversalsByReason.put(PosTransactionReversal.REASON_RESPONSE_TIMEOUT, new AtomicInteger(0));
        reversalsByReason.put(PosTransactionReversal.REASON_CONNECTION_LOST, new AtomicInteger(0));
        reversalsByReason.put(PosTransactionReversal.REASON_SYSTEM_STARTUP, new AtomicInteger(0));
        reversalsByReason.put(PosTransactionReversal.REASON_DATABASE_ERROR, new AtomicInteger(0));
        reversalsByReason.put(PosTransactionReversal.REASON_MANUAL_CLEANUP, new AtomicInteger(0));
        
        // Initialize failure counters
        reversalFailuresByReason.putAll(reversalsByReason);
        reversalFailuresByReason.replaceAll((k, v) -> new AtomicInteger(0));
    }
    
    // =================== METRIC RECORDING METHODS ===================
    
    /**
     * Record that a reversal was initiated
     * @param reason the reason for reversal
     */
    public void recordReversalInitiated(String reason) {
        reversalsInitiated.incrementAndGet();
        reversalsByReason.computeIfAbsent(reason, k -> new AtomicInteger(0)).incrementAndGet();
        
        logger.log("Reversal initiated - Reason: " + reason + 
                  ", Total initiated: " + reversalsInitiated.get());
    }
    
    /**
     * Record that a reversal was completed successfully
     * @param reason the reason for reversal
     * @param durationMs duration in milliseconds
     */
    public void recordReversalCompleted(String reason, long durationMs) {
        reversalsCompleted.incrementAndGet();
        totalReversalDuration.addAndGet(durationMs);
        
        logger.log("Reversal completed - Reason: " + reason + 
                  ", Duration: " + durationMs + "ms" +
                  ", Total completed: " + reversalsCompleted.get());
    }
    
    /**
     * Record that a reversal failed
     * @param reason the reason for original reversal
     * @param errorCode the error code or description
     */
    public void recordReversalFailed(String reason, String errorCode) {
        reversalsFailed.incrementAndGet();
        reversalFailuresByReason.computeIfAbsent(reason, k -> new AtomicInteger(0)).incrementAndGet();
        
        logger.log("Reversal failed - Reason: " + reason + 
                  ", Error: " + errorCode +
                  ", Total failed: " + reversalsFailed.get());
    }
    
    /**
     * Record that a reversal timed out
     * @param reason the reason for original reversal
     */
    public void recordReversalTimedOut(String reason) {
        reversalsTimedOut.incrementAndGet();
        recordReversalFailed(reason, "TIMEOUT");
        
        logger.log("Reversal timed out - Reason: " + reason +
                  ", Total timeouts: " + reversalsTimedOut.get());
    }
    
    // =================== HEALTH MONITORING ===================
    
    /**
     * Perform comprehensive health check of reversal system
     */
    public ReversalHealthStatus checkReversalHealth() {
        try {
            int pendingReversals = transactionAuxUtils.countPendingReversals();
            int staleTemps = transactionAuxUtils.countStaleTemps(MAX_TEMP_AGE_MINUTES);
            double reversalRate = calculateReversalRate();
            
            // Update tracking variables
            lastPendingReversalsCount = pendingReversals;
            lastStaleTempsCount = staleTemps;
            lastReversalRate = reversalRate;
            lastHealthCheck = LocalDateTime.now();
            
            // Determine health status
            boolean isHealthy = pendingReversals <= MAX_PENDING_REVERSALS && 
                               staleTemps == 0 && 
                               reversalRate <= MAX_REVERSAL_RATE_THRESHOLD;
            
            ReversalHealthStatus status = new ReversalHealthStatus(
                isHealthy, pendingReversals, staleTemps, reversalRate,
                getReversalSuccessRate(), getAverageReversalDuration()
            );
            
            if (!isHealthy) {
                logger.log("HEALTH CHECK FAILED: " + status);
                alertOperations("Reversal system health check failed: " + status.toString());
            } else {
                logger.log("Health check passed: " + status);
            }
            
            return status;
            
        } catch (Exception e) {
            logger.log("Error during health check: " + e.getMessage());
            logger.exceptionLog(e);
            return new ReversalHealthStatus(false, -1, -1, -1.0, -1.0, -1.0);
        }
    }
    
    /**
     * Start periodic health checks
     */
    public void startPeriodicHealthChecks() {
        scheduler.scheduleAtFixedRate(
            this::checkReversalHealth,
            MONITORING_INTERVAL_SECONDS, 
            MONITORING_INTERVAL_SECONDS, 
            TimeUnit.SECONDS
        );
        
        logger.log("Started periodic reversal health checks every " + MONITORING_INTERVAL_SECONDS + " seconds");
    }
    
    // =================== METRICS CALCULATION ===================
    
    /**
     * Calculate current reversal rate (reversals / total transactions)
     */
    private double calculateReversalRate() {
        try {
            TransactionAuxUtils.TransactionStatistics stats = transactionAuxUtils.getTransactionStatistics();
            long totalTransactions = stats.successfulTransactions24h + stats.failedTransactions24h;
            int totalReversals = reversalsInitiated.get();
            
            if (totalTransactions == 0) return 0.0;
            return (double) totalReversals / totalTransactions;
            
        } catch (Exception e) {
            logger.log("Error calculating reversal rate: " + e.getMessage());
            return -1.0;
        }
    }
    
    /**
     * Calculate reversal success rate
     */
    public double getReversalSuccessRate() {
        int initiated = reversalsInitiated.get();
        if (initiated == 0) return 100.0;
        
        int completed = reversalsCompleted.get();
        return ((double) completed / initiated) * 100.0;
    }
    
    /**
     * Calculate average reversal duration
     */
    public double getAverageReversalDuration() {
        int completed = reversalsCompleted.get();
        if (completed == 0) return 0.0;
        
        return (double) totalReversalDuration.get() / completed;
    }
    
    /**
     * Get comprehensive metrics summary
     */
    public ReversalMetricsSummary getMetricsSummary() {
        return new ReversalMetricsSummary(
            reversalsInitiated.get(),
            reversalsCompleted.get(),
            reversalsFailed.get(),
            reversalsTimedOut.get(),
            getReversalSuccessRate(),
            getAverageReversalDuration(),
            lastPendingReversalsCount,
            lastStaleTempsCount,
            lastReversalRate,
            new ConcurrentHashMap<>(reversalsByReason),
            new ConcurrentHashMap<>(reversalFailuresByReason),
            lastHealthCheck
        );
    }
    
    // =================== ALERTING ===================
    
    /**
     * Alert operations team
     */
    private void alertOperations(String message) {
        logger.log("OPERATIONS ALERT: " + message);
        
        // In production, this would integrate with:
        // - Email notifications
        // - SMS alerts
        // - Slack/Teams notifications  
        // - PagerDuty/OpsGenie
        // - SNMP traps
        // - Monitoring dashboards (Grafana, etc.)
        
        // For now, just ensure it's prominently logged
        System.err.println("REVERSAL SYSTEM ALERT: " + message);
    }
    
    // =================== SHUTDOWN ===================
    
    /**
     * Shutdown metrics collection
     */
    public void shutdown() {
        scheduler.shutdown();
        try {
            if (!scheduler.awaitTermination(30, TimeUnit.SECONDS)) {
                scheduler.shutdownNow();
            }
        } catch (InterruptedException e) {
            scheduler.shutdownNow();
        }
        
        // Log final metrics summary
        logger.log("Final reversal metrics: " + getMetricsSummary());
    }
    
    // =================== DATA CLASSES ===================
    
    /**
     * Health status of the reversal system
     */
    public static class ReversalHealthStatus {
        public final boolean isHealthy;
        public final int pendingReversals;
        public final int staleTemps;
        public final double reversalRate;
        public final double successRate;
        public final double averageDuration;
        
        public ReversalHealthStatus(boolean isHealthy, int pendingReversals, int staleTemps, 
                                  double reversalRate, double successRate, double averageDuration) {
            this.isHealthy = isHealthy;
            this.pendingReversals = pendingReversals;
            this.staleTemps = staleTemps;
            this.reversalRate = reversalRate;
            this.successRate = successRate;
            this.averageDuration = averageDuration;
        }
        
        @Override
        public String toString() {
            return String.format(
                "ReversalHealthStatus{healthy=%s, pendingReversals=%d, staleTemps=%d, " +
                "reversalRate=%.2f%%, successRate=%.2f%%, avgDuration=%.2fms}",
                isHealthy, pendingReversals, staleTemps, 
                reversalRate * 100, successRate, averageDuration
            );
        }
    }
    
    /**
     * Comprehensive metrics summary
     */
    public static class ReversalMetricsSummary {
        public final int reversalsInitiated;
        public final int reversalsCompleted;
        public final int reversalsFailed;
        public final int reversalsTimedOut;
        public final double successRate;
        public final double averageDuration;
        public final int pendingReversals;
        public final int staleTemps;
        public final double reversalRate;
        public final ConcurrentHashMap<String, AtomicInteger> reversalsByReason;
        public final ConcurrentHashMap<String, AtomicInteger> failuresByReason;
        public final LocalDateTime lastHealthCheck;
        
        public ReversalMetricsSummary(int initiated, int completed, int failed, int timedOut,
                                    double successRate, double averageDuration, int pending, int stale,
                                    double reversalRate, ConcurrentHashMap<String, AtomicInteger> byReason,
                                    ConcurrentHashMap<String, AtomicInteger> failuresByReason,
                                    LocalDateTime lastHealthCheck) {
            this.reversalsInitiated = initiated;
            this.reversalsCompleted = completed;
            this.reversalsFailed = failed;
            this.reversalsTimedOut = timedOut;
            this.successRate = successRate;
            this.averageDuration = averageDuration;
            this.pendingReversals = pending;
            this.staleTemps = stale;
            this.reversalRate = reversalRate;
            this.reversalsByReason = byReason;
            this.failuresByReason = failuresByReason;
            this.lastHealthCheck = lastHealthCheck;
        }
        
        @Override
        public String toString() {
            return String.format(
                "ReversalMetrics{initiated=%d, completed=%d, failed=%d, timedOut=%d, " +
                "successRate=%.2f%%, avgDuration=%.2fms, pending=%d, stale=%d, " +
                "reversalRate=%.2f%%, lastCheck=%s}",
                reversalsInitiated, reversalsCompleted, reversalsFailed, reversalsTimedOut,
                successRate, averageDuration, pendingReversals, staleTemps,
                reversalRate * 100, lastHealthCheck
            );
        }
    }
}