package hr.com.port.ips.eracun.provider.mer;

import hr.com.port.ips.eracun.provider.mer.dto.document.InboxDocumentHeader;
import hr.com.port.ips.eracun.provider.mer.dto.document.OutboxDocumentHeader;
import hr.com.port.ips.eracun.provider.mer.dto.document.QueryInboxResponse;
import hr.com.port.ips.eracun.provider.mer.dto.document.SendResponse;
import hr.com.port.ips.eracun.provider.mer.dto.document.QueryDocumentProcessStatusOutboxResponse;
import hr.com.port.ips.eracun.provider.mer.dto.document.QueryDocumentProcessStatusInboxResponse;
import hr.com.port.ips.eracun.provider.mer.dto.document.ReceiveResponse;
import hr.com.port.ips.eracun.provider.mer.dto.document.QueryOutboxResponse;
import hr.com.port.ips.eracun.provider.mer.dto.ereporting.RejectResponse;
import hr.com.port.ips.eracun.provider.mer.dto.ereporting.MarkPaidResponse;
import hr.com.port.ips.eracun.provider.mer.dto.ereporting.EreportingResponse;
import com.google.gson.Gson;
import hr.com.port.functions.Functions;
import hr.com.port.ips.eracun.provider.mer.enums.MerProcessStatus;
import hr.com.port.ips.eracun.provider.EracunProvider;
import hr.com.port.ips.eracun.provider.ProviderCapabilities;
import hr.com.port.ips.eracun.provider.ProviderException;
import hr.com.port.ips.eracun.provider.model.Ack;
import hr.com.port.ips.eracun.provider.model.Ping;
import hr.com.port.ips.eracun.provider.model.doc.DocumentBinary;
import hr.com.port.ips.eracun.provider.model.doc.DocumentHeader;
import hr.com.port.ips.eracun.provider.model.doc.DocumentRef;
import hr.com.port.ips.eracun.provider.model.doc.DocumentSnapshot;
import hr.com.port.ips.eracun.provider.model.ereporting.EReportingDoc;
import hr.com.port.ips.eracun.provider.model.ereporting.EReportingRequests;
import hr.com.port.ips.eracun.provider.model.ereporting.PaymentReport;
import hr.com.port.ips.eracun.provider.model.ereporting.RejectionReport;
import hr.com.port.ips.eracun.provider.model.ids.DocumentKey;
import hr.com.port.ips.eracun.provider.model.ids.RemoteId;
import hr.com.port.ips.eracun.provider.model.paging.ListFilter;
import hr.com.port.ips.eracun.provider.model.paging.Page;
import hr.com.port.ips.eracun.provider.model.status.ProcessStatusChange;
import hr.com.port.ips.eracun.provider.model.validate.ValidationResult;
import hr.com.port.ips.eracun.provider.mer.mapper.MerModelMapper;
import org.apache.log4j.Logger;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

public class MerEracunProvider implements EracunProvider {

    private static final Logger logger = Logger.getLogger(MerEracunProvider.class);

    private final MerClient mer;
    private final ProviderCapabilities caps;
    private final Gson gson = new Gson();

    /** Preporuka: injektiraj već konfiguriran MerClient (ima username/password/companyId/bu/softwareId i baseUrl). */
    public MerEracunProvider(MerClient merClient) {
        this.mer = merClient;

        ProviderCapabilities c = new ProviderCapabilities();
        c.supportsPdf = false;             // MER nema univerzalni “visualization” PDF endpoint
        c.supportsXmlGet = true;           // preko /receive
        c.supportsProcessStatusChange = false; // ne generički; koristi markPaid/reject/ereporting specifično
        c.supportsValidation = false;      // nema posebnog MER validate endpointa
        c.supportsEReporting = true;       // /apis/v2/fiscalization/eReporting
        c.supportsListByModified = true;   // queryDocumentProcessStatus* podržava byUpdateDate
        c.supportsBanking = false;
        c.supportsAms = false;
        c.pagingMode = "OFFSET";           // simuliramo offset preko limit/offset u neutralnom modelu
        this.caps = c;
    }

    @Override public ProviderCapabilities capabilities() { return caps; }

    // --- SEND ----------------------------------------------------------------
    @Override
    public DocumentRef send(String ublXml, String softwareId) throws ProviderException {
        try {
            SendResponse r = mer.send(ublXml); // softwareId već živi u MerClient-u
            if (r == null) throw new ProviderException("MER send soft-failed (gate open or null response).");
            return MerModelMapper.toRef(String.valueOf(r.ElectronicId), r.Created);
        } catch (Exception ex) {
            logger.error(new Functions().logging(ex));
            throw new ProviderException("MER send failed", ex);
        }
    }

    // --- GET XML / PDF / SNAPSHOT -------------------------------------------
    @Override
    public DocumentBinary getXml(RemoteId id) throws ProviderException {
        try {
            // MER “receive” vraća sadržaj XML-a (uglavnom za INBOX; za OUTBOX obično nema retrieva)
            ReceiveResponse rr = mer.receive(new hr.com.port.ips.eracun.provider.mer.dto.document.ReceiveRequest(
                    mer.getUsername(), mer.getPassword(), mer.getCompanyId(), mer.getCompanyBu(), mer.getSoftwareId(),
                    Long.valueOf(id.value)
            ));
            if (rr == null || rr.xmlContent == null) {
                throw new ProviderException("MER receive returned empty xmlContent.");
            }
            DocumentBinary b = new DocumentBinary();
            b.mime = "application/xml";
            b.contentBase64 = rr.xmlContent;
            return b;
        } catch (Exception ex) {
            logger.error(new Functions().logging(ex));
            throw new ProviderException("MER getXml failed", ex);
        }
    }

    @Override
    public DocumentBinary getPdf(RemoteId id) throws ProviderException {
        // MER nema generički PDF visualization → vraćamo not-supported
        throw new ProviderException("MER getPdf not supported by provider.");
    }

    @Override
    public DocumentSnapshot get(RemoteId id) throws ProviderException {
        try {
            Long eid = Long.valueOf(id.value);

            // prvo OUTBOX (ako je naš, brže će se naći), potom INBOX
            QueryDocumentProcessStatusOutboxResponse out = mer.queryDocumentProcessStatusOutbox(
                    eid, null, null, null, null, null, Boolean.TRUE);
            if (out != null && out.getDocumentHeaders() != null && !out.getDocumentHeaders().isEmpty()) {
                OutboxDocumentHeader h = out.getDocumentHeaders().get(0);
                return MerModelMapper.toSnapshot(h, mer.getCompanyId());
            }

            QueryDocumentProcessStatusInboxResponse in = mer.queryDocumentProcessStatusInbox(
                    eid, null, null, null, null, null, Boolean.TRUE);
            if (in != null && in.getDocumentHeaders() != null && !in.getDocumentHeaders().isEmpty()) {
                InboxDocumentHeader h = in.getDocumentHeaders().get(0);
                return MerModelMapper.toSnapshot(h, mer.getCompanyId());
            }

            throw new ProviderException("MER get: document not found for ElectronicId=" + id.value);
        } catch (ProviderException pe) {
            throw pe;
        } catch (Exception ex) {
            logger.error(new Functions().logging(ex));
            throw new ProviderException("MER get(status) failed", ex);
        }
    }

    // --- LIST INCOMING / OUTGOING -------------------------------------------
    @Override
    public Page<DocumentHeader> listIncoming(ListFilter f) throws ProviderException {
        try {
            QueryInboxResponse resp = mer.queryInbox(new hr.com.port.ips.eracun.provider.mer.dto.document.QueryInboxRequest(
                    mer.getUsername(), mer.getPassword(), mer.getCompanyId(), mer.getCompanyBu(), mer.getSoftwareId()
            ));
            List<DocumentHeader> items = new ArrayList<>();
            if (resp != null && resp.getDocumentHeaders() != null) {
                for (InboxDocumentHeader h : resp.getDocumentHeaders()) {
                    items.add(MerModelMapper.toHeader(h, mer.getCompanyId()));
                }
            }
            Page<DocumentHeader> page = new Page<>();
            page.items = items;
            return page;
        } catch (Exception ex) {
            logger.error(new Functions().logging(ex));
            throw new ProviderException("MER listIncoming failed", ex);
        }
    }

    @Override
    public Page<DocumentHeader> listOutgoing(ListFilter f) throws ProviderException {
        try {
            QueryOutboxResponse resp = mer.queryOutbox(new hr.com.port.ips.eracun.provider.mer.dto.document.QueryOutboxRequest(
                    mer.getUsername(), mer.getPassword(), mer.getCompanyId(), mer.getCompanyBu(), mer.getSoftwareId()
            ));
            List<DocumentHeader> items = new ArrayList<>();
            if (resp != null && resp.getDocumentHeaders() != null) {
                for (OutboxDocumentHeader h : resp.getDocumentHeaders()) {
                    items.add(MerModelMapper.toHeader(h, mer.getCompanyId()));
                }
            }
            Page<DocumentHeader> page = new Page<>();
            page.items = items;
            return page;
        } catch (Exception ex) {
            logger.error(new Functions().logging(ex));
            throw new ProviderException("MER listOutgoing failed", ex);
        }
    }

    // --- PROCESS STATUS CHANGE / VALIDATE ------------------------------------
    @Override
    public Ack changeProcessStatus(RemoteId id, ProcessStatusChange change) throws ProviderException {
        // MER ima specifične akcije (markPaid/reject); generički change nije podržan.
        Ack a = new Ack();
        a.ok = false;
        a.raw = "MER: changeProcessStatus not supported (use reportPayment/reportRejection).";
        return a;
    }

    @Override
    public ValidationResult validate(String ublXml) {
        // Nema MER validate endpointa: vraćamo prazan rezultat (sve ok).
        ValidationResult vr = new ValidationResult();
        vr.issues = new ArrayList<>();
        return vr;
    }

    // --- eREPORTING / PAID / REJECT -----------------------------------------
    @Override
    public Ack reportDocument(EReportingDoc req) throws ProviderException {
        try {
            // mapiranje: xml + (optional) deliveryDate + invoiceType
            EreportingResponse r = mer.eReporting(req.xml, req.deliveryDate, req.isCopy, req.invoiceType);
            Ack a = new Ack();
            a.ok = (r != null && Boolean.TRUE.equals(r.getIsSuccess()));
            a.raw = (r == null ? "null" : ("timestamp=" + r.getReportingTimestamp()));
            return a;
        } catch (Exception ex) {
            logger.error(new Functions().logging(ex));
            throw new ProviderException("MER eReporting failed", ex);
        }
    }

    @Override
    public Ack reportPayment(PaymentReport pr) throws ProviderException {
        try {
            if (pr.id == null) {
                throw new ProviderException("MER markPaid requires ElectronicId (RemoteId).");
            }
            MarkPaidResponse r = mer.markPaid(
                    Long.parseLong(pr.id.value),
                    pr.paymentDate,
                    pr.paidAmount == null ? null : pr.paidAmount.doubleValue(),
                    pr.paymentMethod
            );
            Ack a = new Ack();
            a.ok = (r != null && Boolean.TRUE.equals(r.getIsSuccess()));
            a.raw = (r == null ? "null" : ("timestamp=" + r.getFiscalizationTimestamp()));
            return a;
        } catch (Exception ex) {
            logger.error(new Functions().logging(ex));
            throw new ProviderException("MER reportPayment failed", ex);
        }
    }

    @Override
    public Ack reportRejection(RejectionReport rr) throws ProviderException {
        try {
            if (rr.id == null) {
                throw new ProviderException("MER reject requires ElectronicId (RemoteId).");
            }
            // rr.rejectionType očekujemo kao "N"|"U"|"O" – MER prima kod tipa teksta
            RejectResponse r = mer.reject(
                    Long.parseLong(rr.id.value),
                    rr.rejectionDate,
                    rr.rejectionType,    // npr. "N" / "U" / "O"
                    rr.reason
            );
            Ack a = new Ack();
            a.ok = (r != null && r.Success);
            a.raw = (r == null ? "null" : r.Message);
            return a;
        } catch (Exception ex) {
            logger.error(new Functions().logging(ex));
            throw new ProviderException("MER reportRejection failed", ex);
        }
    }

    @Override
    public EReportingRequests listEReportingRequests(DocumentKey key) throws ProviderException {
        // MER nema uniformni “list eReporting requests” endpoint → vraćamo prazno
        EReportingRequests out = new EReportingRequests();
        out.items = new EReportingRequests.RequestItem[0];
        return out;
    }

    // --- PING ----------------------------------------------------------------
    @Override
    public Ping ping() {
        // MER nema klasičan ping; vratimo statično OK (ili eventualno probati benigni poziv).
        Ping p = new Ping();
        p.status = "OK";
        p.message = "MER adapter alive";
        return p;
    }
}
