package hr.com.port.ips.eracun.scheduler;

import hr.com.port.functions.Functions;
import hr.com.port.ips.eracun.provider.mer.MerClient;
import hr.com.port.ips.eracun.provider.mer.enums.MerFiscalizationStatus;
import hr.com.port.ips.eracun.resilience.mer.MerAvailabilityGate;
import hr.com.port.ips.eracun.service.EracunSyncService;
import hr.com.port.ips.eracun.service.EracunServiceUnavailableException;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;

import hr.com.port.ips.poruke.PorukaDao;
import hr.com.port.ips.poruke.PorukaModel;

public class FiscalizationStatusJob implements Runnable {
    private static final Logger logger = Logger.getLogger(FiscalizationStatusJob.class);

    /** per-endpoint ključ za circuit/gate kontrolu */
    private static final String OPKEY = "FISCALIZATION_STATUS";
    private static final String PROVIDER = "MER";

    private final Supplier<Connection> connectionSupplier;
    private final Supplier<MerClient> merClientSupplier;
    private final EracunSyncService syncService;
    private final int batchSize;
    private final int coolDownMinutes;

    private final PorukaDao porukaDao = new PorukaDao();

    public FiscalizationStatusJob(Supplier<Connection> connectionSupplier,
                                  Supplier<MerClient> merClientSupplier,
                                  EracunSyncService syncService,
                                  int batchSize,
                                  int coolDownMinutes) {
        this.connectionSupplier = connectionSupplier;
        this.merClientSupplier = merClientSupplier;
        this.syncService = syncService;
        this.batchSize = batchSize;
        this.coolDownMinutes = coolDownMinutes;
    }

    @Override
    public void run() {
        // Ako je circuit OPEN za ovaj op → sticky lokalna poruka + preskoči ciklus
        if (!MerAvailabilityGate.allow(OPKEY)) {
            logger.warn("FiscalizationStatusJob – gate OPEN, preskačem ciklus.");
            Connection c = null;
            try {
                c = connectionSupplier.get();
                ensureOutageOpen(c, "{\"provider\":\"MER\",\"op\":\"FISCALIZATION_STATUS\",\"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;
        }

        Connection conn = null;
        try {
            conn = connectionSupplier.get();
            conn.setAutoCommit(false);

            List<Long> candidates = findOutgoingNotFiscalized(conn, batchSize, coolDownMinutes);
            if (candidates.isEmpty()) {
                // prazan ciklus relaksira circuit i briše sticky poruku (ako postoji)
                MerAvailabilityGate.onSuccess(OPKEY);
                resolveOutage(conn);
                conn.commit();
                return;
            }

            MerClient mer = merClientSupplier.get();

            for (Long eid : candidates) {
                try {
                    // messageType=0 (SENDER) po v1.5 / v1.8
                    syncService.syncFiscalizationStatus(conn, mer, eid, 0);
                    conn.commit();

                    // uspješan kontakt → relaksiraj circuit i ukloni sticky poruku
                    MerAvailabilityGate.onSuccess(OPKEY);
                    resolveOutage(conn);

                } catch (EracunServiceUnavailableException ex) {
                    // Nedostupnost posrednika → SOFT-FAIL: bez error loga prema centrali
                    logger.warn(new Functions().logging(ex));
                    MerAvailabilityGate.onFailure(OPKEY);
                    ensureOutageOpen(conn, "{\"provider\":\"MER\",\"op\":\"FISCALIZATION_STATUS\",\"reason\":\"unavailable\"}");
                    try { conn.rollback(); } catch (Exception ignore) {}

                } catch (IOException ex) {
                    // Ostale I/O greške (tvrde) – tretiramo kao prave greške
                    logger.error(new Functions().logging(ex));
                    MerAvailabilityGate.onFailure(OPKEY);
                    try { conn.rollback(); } catch (Exception ignore) {}

                } catch (SQLException ex) {
                    // DB greška – ne diramo circuit MER-a
                    logger.error(new Functions().logging(ex));
                    try { conn.rollback(); } catch (Exception ignore) {}
                }
            }
        } catch (Exception ex) {
            logger.error(new Functions().logging(ex));
            try { if (conn != null) conn.rollback(); } catch (Exception ignore) {}
        } finally {
            try { if (conn != null) conn.close(); } catch (Exception ignore) {}
        }
    }

    // OUTBOX kandidati: fiskaliziran NULL/UNKNOWN(-1)/FAILED(1),
    // bez svježeg FISC loga unutar coolDownMinutes.
    // ✎ prilagodi filter za "izlazni" prema tvojoj koloni.
    private List<Long> findOutgoingNotFiscalized(Connection conn, int limit, int coolDownMinutes) throws SQLException {
        List<Long> result = new ArrayList<Long>();

        final String sql =
            "SELECT d.electronic_id " +
            "  FROM eracun_dokument d " +
            " WHERE d.electronic_id IS NOT NULL " +
            "   AND (d.fiskaliziran_status IS NULL OR d.fiskaliziran_status IN (" + MerFiscalizationStatus.UNKNOWN.getId() + ", " + MerFiscalizationStatus.FAILED.getId() + ")) " +
            "   AND d.izlazni = 1 " +
            "   AND NOT EXISTS ( " +
            "       SELECT 1 FROM eracun_dokument_log l " +
            "        WHERE l.electronic_id = d.electronic_id " +
            "          AND l.akcija = 'FISC20_STATUS' " +
            "          AND l.datum_promjene > (NOW() - INTERVAL ? MINUTE) " +
            "   ) " +
            " ORDER BY d.electronic_id " +
            " LIMIT ?";

        try (PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setInt(1, coolDownMinutes);
            ps.setInt(2, limit);
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) result.add(rs.getLong(1));
                logger.info("Nađeno " + result.size() + " nefiskaliziranih");
            }
        }
        return result;
    }

    // --- Helpers za lokalne "sticky" outage poruke ---

    private String outageCentralId() {
        return "LOCAL:OUTAGE:" + PROVIDER + ":" + OPKEY;
    }

    private void ensureOutageOpen(Connection conn, String reasonJson) {
        try {
            PorukaModel m = new PorukaModel();
            m.setCentralniId(outageCentralId());
            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("MojeRačun (MER) nedostupan");
            m.setTekst("Operacija „Fiskalizacija — status” 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));
        }
    }

    private void resolveOutage(Connection conn) {
        try {
            porukaDao.deleteByCentralId(outageCentralId(), conn);
        } catch (Exception ex) {
            logger.warn(new Functions().logging(ex));
        }
    }
}