package hr.com.port.ips.eracun.scheduler;

import hr.com.port.functions.Functions;
import hr.com.port.ips.eracun.boot.EracunSyncConfig;
import hr.com.port.ips.eracun.provider.mer.MerClient;
import hr.com.port.ips.eracun.service.EracunSyncService;
import org.apache.log4j.Logger;

import java.sql.Connection;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;


public class FiscalizationScheduler {
    private static final Logger logger = Logger.getLogger(FiscalizationScheduler.class);

    private final EracunSyncConfig cfg;
    private final EracunSyncService syncService;
    private final Supplier<MerClient> merClientSupplier;
    private final Supplier<Connection> connectionSupplier;
    private final int batchSize;
    private final int coolDownMinutes;

    private final ScheduledExecutorService exec;
    private ScheduledFuture<?> fiscalization20Future;
	private ScheduledFuture<?> markPaidFuture;
	private ScheduledFuture<?> rejectFuture;
	private ScheduledFuture<?> ereportingFuture;

    public FiscalizationScheduler(EracunSyncConfig cfg,
                                        EracunSyncService syncService,
                                        Supplier<MerClient> merClientSupplier,
                                        Supplier<Connection> connectionSupplier,
                                        int batchSize,
                                        int coolDownMinutes) {
        this.cfg = cfg;
        this.syncService = syncService;
        this.merClientSupplier = merClientSupplier;
        this.connectionSupplier = connectionSupplier;
        this.batchSize = batchSize;
        this.coolDownMinutes = coolDownMinutes;

        this.exec = Executors.newScheduledThreadPool(1, new ThreadFactory() {
            private final AtomicInteger n = new AtomicInteger(1);
            @Override public Thread newThread(Runnable r) {
                Thread t = new Thread(r, "eracun-fiscalization-sync-" + n.getAndIncrement());
                t.setDaemon(true);
                return t;
            }
        });
    }

    public synchronized void start() {
        
		if (isFiscalization20Enabled()) {        
			long delaySec  = safe(getFiscalization20InitialDelaySeconds(), 10L);
			long periodSec = safe(getFiscalization20PeriodSeconds(), 60L);

			FiscalizationStatusJob job = new FiscalizationStatusJob(
					connectionSupplier,
					merClientSupplier,
					syncService,
					batchSize,
					coolDownMinutes
			);
			fiscalization20Future = exec.scheduleAtFixedRate(job, delaySec, periodSec, TimeUnit.SECONDS);
			logger.info("Fiscalization scheduler STARTED (delay=" + delaySec + "s, period=" + periodSec +
					"s, batch=" + batchSize + ", cooldown=" + coolDownMinutes + "m)");
		}else{
			logger.info("Fiscalization scheduler DISABLED");           
		}
		
		if (isMarkPaidEnabled()) {        
			long delaySec  = safe(getMarkPaidInitialDelaySeconds(), 10L);
			long periodSec = safe(getMarkPaidPeriodSeconds(), 60L);

			MarkPaidRetryJob job = new MarkPaidRetryJob(
					merClientSupplier,
					connectionSupplier,					
					syncService,
					batchSize
			);
			markPaidFuture = exec.scheduleAtFixedRate(job, delaySec, periodSec, TimeUnit.SECONDS);
			logger.info("MarkPaid retry scheduler STARTED (delay=" + delaySec + "s, period=" + periodSec +
					"s, batch=" + batchSize + ", cooldown=" + coolDownMinutes + "m)");
		}else{
			logger.info("MarkPaid scheduler DISABLED");            
		}
		
		if (isRejectEnabled()) {        
			long delaySec  = safe(getRejectInitialDelaySeconds(), 10L);
			long periodSec = safe(getRejectPeriodSeconds(), 60L);

			RejectRetryJob job = new RejectRetryJob(
					merClientSupplier,
					connectionSupplier,					
					syncService,
					batchSize
			);
			rejectFuture = exec.scheduleAtFixedRate(job, delaySec, periodSec, TimeUnit.SECONDS);
			logger.info("Reject retry scheduler STARTED (delay=" + delaySec + "s, period=" + periodSec +
					"s, batch=" + batchSize + ", cooldown=" + coolDownMinutes + "m)");
		}else{
			logger.info("Reject scheduler DISABLED");            
		}
		
		if (isEreportingEnabled()) {
			long delaySec  = safe(getEreportingInitialDelaySeconds(), 10L);
			long periodSec = safe(getEreportingPeriodSeconds(), 60L);

			EreportingRetryJob job = new EreportingRetryJob(
					merClientSupplier,
					connectionSupplier,
					batchSize
			);
			ereportingFuture = exec.scheduleAtFixedRate(job, delaySec, periodSec, TimeUnit.SECONDS);
			logger.info("Ereporting retry scheduler STARTED (delay=" + delaySec + "s, period=" + periodSec +
					"s, batch=" + batchSize + ", cooldown=" + coolDownMinutes + "m)");
		} else {
			logger.info("Ereporting scheduler DISABLED");
		}
    }

    public synchronized void stop() {
        try {
            if (fiscalization20Future != null) {
                fiscalization20Future.cancel(false);
                fiscalization20Future = null;
            }
			if (markPaidFuture != null) {
                markPaidFuture.cancel(false);
                markPaidFuture = null;
            }
			if (rejectFuture != null) {
                rejectFuture.cancel(false);
                rejectFuture = null;
            }
			if (ereportingFuture != null) {
				ereportingFuture.cancel(false);
				ereportingFuture = null;
			}
        } catch (Exception ex) {
            logger.error(new Functions().logging(ex));
        }
        exec.shutdownNow();
        logger.info("Fiscalization and MarkPaid and Reject and Ereporting schedulers STOPPED");
    }

    private static long safe(Long v, long def) { return (v != null && v > 0) ? v : def; }	

    // Reflection getteri 
    private boolean isFiscalization20Enabled() {
        try { return (Boolean) EracunSyncConfig.class.getMethod("fiscalization20Enabled").invoke(cfg); }
        catch (Exception ignore) { return true; }
    }
    private Long getFiscalization20InitialDelaySeconds() {
        try { return (Long) EracunSyncConfig.class.getMethod("fiscalization20InitialDelaySeconds").invoke(cfg); }
        catch (Exception ignore) { return 10L; }
    }
    private Long getFiscalization20PeriodSeconds() {
        try { return (Long) EracunSyncConfig.class.getMethod("fiscalization20PeriodSeconds").invoke(cfg); }
        catch (Exception ignore) { return 60L; }
    }
	
	private boolean isMarkPaidEnabled() {
        try { return (Boolean) EracunSyncConfig.class.getMethod("markPaidEnabled").invoke(cfg); }
        catch (Exception ignore) { return true; }
    }
    private Long getMarkPaidInitialDelaySeconds() {
        try { return (Long) EracunSyncConfig.class.getMethod("markPaidInitialDelaySeconds").invoke(cfg); }
        catch (Exception ignore) { return 10L; }
    }
    private Long getMarkPaidPeriodSeconds() {
        try { return (Long) EracunSyncConfig.class.getMethod("markPaidPeriodSeconds").invoke(cfg); }
        catch (Exception ignore) { return 60L; }
    }
	
	private boolean isRejectEnabled() {
        try { return (Boolean) EracunSyncConfig.class.getMethod("rejectEnabled").invoke(cfg); }
        catch (Exception ignore) { return true; }
    }
    private Long getRejectInitialDelaySeconds() {
        try { return (Long) EracunSyncConfig.class.getMethod("rejectInitialDelaySeconds").invoke(cfg); }
        catch (Exception ignore) { return 10L; }
    }
    private Long getRejectPeriodSeconds() {
        try { return (Long) EracunSyncConfig.class.getMethod("rejectPeriodSeconds").invoke(cfg); }
        catch (Exception ignore) { return 60L; }
    }
	
	private boolean isEreportingEnabled() {
		try { return (Boolean) EracunSyncConfig.class.getMethod("ereportingEnabled").invoke(cfg); }
		catch (Exception ignore) { return true; }
	}
	private Long getEreportingInitialDelaySeconds() {
		try { return (Long) EracunSyncConfig.class.getMethod("ereportingInitialDelaySeconds").invoke(cfg); }
		catch (Exception ignore) { return 10L; }
	}
	private Long getEreportingPeriodSeconds() {
		try { return (Long) EracunSyncConfig.class.getMethod("ereportingPeriodSeconds").invoke(cfg); }
		catch (Exception ignore) { return 60L; }
	}
}