package hr.com.port.ips.eracun.scheduler;

import hr.com.port.functions.Functions;
import hr.com.port.ips.eracun.resilience.mer.MerAvailabilityGate;
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.function.Supplier;

// Posao koji pokreće sinkronizaciju procesnih statusa za INBOX ili OUTBOX.
// Oslanja se na javne metode u EracunSyncService:
//  - syncProcessStatusInbox()
//  - syncProcessStatusOutbox()
//
// Pravila:
//  - prije pokretanja provjeri gate za odgovarajući opKey (INBOX/OUTBOX).
//  - na uspjeh → MerAvailabilityGate.onSuccess(opKey) + ukloni sticky poruku
//  - na nedostupnost servisa (EracunServiceUnavailableException) → SOFT-FAIL (warn) + MerAvailabilityGate.onFailure(opKey) + sticky poruka
//  - na ostale greške → logiraj error, bez diranja gate-a.
//
public class ProcessStatusJob implements Runnable {

    private static final Logger logger = Logger.getLogger(ProcessStatusJob.class);

    public static final int INBOX  = 1;
    public static final int OUTBOX = 2;

    private static final String PROVIDER = "MER";

    private final EracunSyncService syncService;
    private final int direction; // INBOX ili OUTBOX
    private final String name;

    private final Supplier<Connection> connectionSupplier;
    private final PorukaDao porukaDao = new PorukaDao();

    public ProcessStatusJob(EracunSyncService syncService, int direction, Supplier<Connection> connectionSupplier) {
        this.syncService = syncService;
        this.direction = direction;
        this.name = direction == INBOX ? "ProcessStatus-INBOX" : "ProcessStatus-OUTBOX";
        this.connectionSupplier = connectionSupplier;
    }

    @Override
    public void run() {
        final String opKey = (direction == INBOX) ? "Q_PSTAT_IN" : "Q_PSTAT_OUT";

        // Gate pre-check: ako je circuit OPEN, preskačemo ciklus i otvorimo/održimo sticky poruku
        if (!MerAvailabilityGate.allow(opKey)) {
            logger.warn("▶ " + name + " – gate OPEN za " + opKey + ", preskačem ciklus.");
            Connection c = null;
            try {
                c = connectionSupplier.get();
                ensureOutageOpen(c, opKey, "{\"provider\":\"MER\",\"op\":\"" + opKey + "\",\"reason\":\"circuit_open\"}");
                c.commit();
            } catch (Exception ex) {
                logger.warn(new Functions().logging(ex));
                try { if (c != null) c.rollback(); } catch (Exception ignore) {}
            } finally {
                try { if (c != null) c.close(); } catch (Exception ignore) {}
            }
            return;
        }

        logger.info("▶ " + name + " start");
        try {
            switch (direction) {
                case INBOX:
                    // može baciti EracunServiceUnavailableException (SOFT-FAIL)
                    syncService.syncProcessStatusInbox();
                    // uspješan kontakt → relaksiraj circuit i ukloni sticky poruku
                    MerAvailabilityGate.onSuccess(opKey);
                    resolveOutageSafe(opKey);
                    break;

                case OUTBOX:
                    syncService.syncProcessStatusOutbox();
                    MerAvailabilityGate.onSuccess(opKey);
                    resolveOutageSafe(opKey);
                    break;

                default:
                    logger.warn("Nepoznat direction u ProcessStatusJob: " + direction);
                    break;
            }
        } catch (EracunServiceUnavailableException ex) {
            // mreža/5xx/prelazna nedostupnost → SOFT-FAIL
            logger.warn(new Functions().logging(ex));
            MerAvailabilityGate.onFailure(opKey);
            ensureOutageOpenSafe(opKey, "{\"provider\":\"MER\",\"op\":\"" + opKey + "\",\"reason\":\"unavailable\"}");
        } catch (Exception ex) {
            // ostalo → trajna ili aplikativna greška (ne diramo circuit)
            logger.error(new Functions().logging(ex));
        } finally {
            logger.info("■ " + name + " end");
        }
    }

    // --- 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 = connectionSupplier.get();
            ensureOutageOpen(conn, opKey, reasonJson);
            conn.commit();
        } catch (Exception ex) {
            logger.warn(new Functions().logging(ex));
            try { if (conn != null) conn.rollback(); } catch (Exception ignore) {}
        } finally {
            try { if (conn != null) conn.close(); } catch (Exception ignore) {}
        }
    }

    private void resolveOutageSafe(String opKey) {
        Connection conn = null;
        try {
            conn = connectionSupplier.get();
            porukaDao.deleteByCentralId(outageCentralId(opKey), conn);
            conn.commit();
        } catch (Exception ex) {
            logger.warn(new Functions().logging(ex));
            try { if (conn != null) conn.rollback(); } catch (Exception ignore) {}
        } 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
            m.setPrikazatiOd(new Timestamp(System.currentTimeMillis()));
            m.setVrijediDo(null);
            m.setNaslov("Moj-eRačun nedostupan");
            m.setTekst("Operacija „Process status (" + ((direction == INBOX) ? "INBOX" : "OUTBOX") + ")” 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));
        }
    }
}