package hr.com.port.ips.eracun.scheduler;

import hr.com.port.connectionPool.__Pool;
import hr.com.port.functions.Functions;
import hr.com.port.ips.eracun.boot.EracunSyncConfig;
import hr.com.port.ips.eracun.service.EracunServiceUnavailableException;
import hr.com.port.ips.eracun.service.EracunSyncService;
import hr.com.port.ips.poruke.PorukaDao;
import hr.com.port.ips.poruke.PorukaModel;
import org.apache.log4j.Logger;

import java.sql.Connection;
import java.sql.Timestamp;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

public final class SyncJobsScheduler {

    private final ScheduledExecutorService exec;
    private final ReentrantLock inboxLock = new ReentrantLock();
    private final ReentrantLock outboxLock = new ReentrantLock();

    private final EracunSyncConfig cfg;
    private final EracunSyncService syncService;

    private ScheduledFuture<?> inboxFuture;
    private ScheduledFuture<?> outboxFuture;

    static Logger logger = Logger.getLogger(SyncJobsScheduler.class);
    
    private final PorukaDao porukaDao = new PorukaDao();
    private static final String PROVIDER = "MER";
    private static final String OP_INBOX  = "Q_INBOX";
    private static final String OP_OUTBOX = "Q_OUTBOX";

    public SyncJobsScheduler(EracunSyncConfig cfg, EracunSyncService syncService) {
        this.cfg = cfg;
        this.syncService = syncService;
        this.exec = Executors.newScheduledThreadPool(2, new ThreadFactory() {
            private final AtomicInteger n = new AtomicInteger(1);
            @Override public Thread newThread(Runnable r) {
                Thread t = new Thread(r, "eracun-sync-" + n.getAndIncrement());
                t.setDaemon(true);
                return t;
            }
        });
    }

    public void start() {
        long delay = cfg.initialDelaySeconds();
        if (cfg.inboxEnabled()) {
            inboxFuture = exec.scheduleAtFixedRate(new Runnable() {
                @Override public void run() {
                    safeRunInbox();
                }
            }, delay, cfg.inboxPeriodSeconds(), TimeUnit.SECONDS);
        }
        if (cfg.outboxEnabled()) {
            outboxFuture = exec.scheduleAtFixedRate(new Runnable() {
                @Override public void run() {
                    safeRunOutbox();
                }
            }, delay, cfg.outboxPeriodSeconds(), TimeUnit.SECONDS);
        }
        logger.info("SyncJobsScheduler started (inbox={" +
                cfg.inboxEnabled() + "}, outbox={" + cfg.outboxEnabled() + "})");
    }

    public void stop() {
        if (inboxFuture != null) inboxFuture.cancel(false);
        if (outboxFuture != null) outboxFuture.cancel(false);
        exec.shutdown();
        try {
            if (!exec.awaitTermination(5, TimeUnit.SECONDS)) {
                exec.shutdownNow();
            }
        } catch (InterruptedException ex) {
            logger.error(new Functions().logging(ex));
            Thread.currentThread().interrupt();
        }
        logger.info("SyncJobsScheduler stopped.");
    }

    void safeRunInbox() {
        if (!inboxLock.tryLock()) {
            logger.debug("Inbox job već radi — preskačem iteraciju.");
            return;
        }
        try {
            // 1) pročitati sync state (token/marker/vremenska točka) iz tvog DAO-a
            // 2) pozvati API QueryInbox
            // 3) predati response u tvoju postojeću obradu
            // 4) ažurirati sync state
            syncService.pollInboxOnce();

            // Uspješan kontakt → ukloni sticky poruku za INBOX
            resolveOutageSafe(OP_INBOX);

        } catch (EracunServiceUnavailableException ex) {
            // MER timeout / mreža – SOFT-FAIL → sticky poruka (bez logger.error)
            logger.warn(new Functions().logging(ex));
            ensureOutageOpenSafe(OP_INBOX,
                    "{\"provider\":\"" + PROVIDER + "\",\"op\":\"" + OP_INBOX + "\",\"reason\":\"unavailable\"}");

        } catch (Exception ex) {
            // Ostalo su "prave" greške
            logger.error(new Functions().logging(ex));
        } finally {
            inboxLock.unlock();
        }
    }

    private void safeRunOutbox() {
        if (!outboxLock.tryLock()) {
            logger.debug("Outbox job već radi — preskačem iteraciju.");
            return;
        }
        try {
            // 1) uzmi skup dokumenata za provjeru statusa
            // 2) QueryOutbox / ili pojedinačni status endpoint
            // 3) processQueryOutboxResponse / persist promjene statusa (i log tablica, ako imaš)
            // 4) ažuriraj sync state / timestamps
            syncService.pollOutboxStatusesOnce();

            // Uspješan kontakt → ukloni sticky poruku za OUTBOX
            resolveOutageSafe(OP_OUTBOX);

        } catch (EracunServiceUnavailableException ex) {
            // MER timeout / mreža – SOFT-FAIL → sticky poruka (bez logger.error)
            logger.warn(new Functions().logging(ex));
            ensureOutageOpenSafe(OP_OUTBOX,
                    "{\"provider\":\"" + PROVIDER + "\",\"op\":\"" + OP_OUTBOX + "\",\"reason\":\"unavailable\"}");

        } catch (Exception ex) {
            logger.error(new Functions().logging(ex));
        } finally {
            outboxLock.unlock();
        }
    }

    // --- Sticky outage helpers (LOCAL:OUTAGE:MER:{opKey}) ---

    private String outageCentralId(String opKey) {
        return "LOCAL:OUTAGE:" + PROVIDER + ":" + opKey;
    }

    private void ensureOutageOpenSafe(String opKey, String reasonJson) {
        Connection conn = null;
        try {
            conn = new __Pool(null).getConnection();
            ensureOutageOpen(conn, opKey, reasonJson);
            // autocommit najčešće uključen; ako nije, dao.insert/upsertFromCentral rješava transakciju
        } catch (Exception ex) {
            logger.warn(new Functions().logging(ex));
        } finally {
            try { if (conn != null) conn.close(); } catch (Exception ignore) {}
        }
    }

    private void resolveOutageSafe(String opKey) {
        Connection conn = null;
        try {
            conn = new __Pool(null).getConnection();
            porukaDao.deleteByCentralId(outageCentralId(opKey), conn);
        } catch (Exception ex) {
            logger.warn(new Functions().logging(ex));
        } finally {
            try { if (conn != null) conn.close(); } catch (Exception ignore) {}
        }
    }

    private void ensureOutageOpen(Connection conn, String opKey, String reasonJson) {
        try {
            PorukaModel m = new PorukaModel();
            m.setCentralniId(outageCentralId(opKey));
            m.setCentralniHash("v1");
            m.setAplikacijaId(0);
            m.setModulId(0);
            m.setPrioritet(100);
            m.setNacinIsporuke(1); // popup/log prema konfiguraciji
            m.setPrikazatiOd(new Timestamp(System.currentTimeMillis()));
            m.setVrijediDo(null);
            m.setNaslov("Moj-eRačun nedostupan");
            m.setTekst(opKey.equals(OP_INBOX)
                    ? "Operacija „Inbox (QueryInbox)” trenutno nije dostupna. Sustav će pokušati ponovno automatski."
                    : "Operacija „Outbox – statusi” trenutno nije dostupna. Sustav će pokušati ponovno automatski.");
            m.setPayloadJson(reasonJson);
            m.setStanje(0);
            m.setMozeObrisati(true);
            m.setCentralCreatedAt(new Timestamp(System.currentTimeMillis()));
            m.setPrimljenoAt(new Timestamp(System.currentTimeMillis()));
            porukaDao.upsertFromCentral(m, conn);
        } catch (Exception ex) {
            logger.warn(new Functions().logging(ex));
        }
    }
}