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

import hr.com.port.ips.eracun.provider.mer.enums.MerProcessStatus;
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.model.doc.DocumentBinary;
import hr.com.port.ips.eracun.provider.model.doc.DocumentHeader;
import hr.com.port.ips.eracun.provider.model.doc.DocumentKind;
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.ids.DocumentKey;
import hr.com.port.ips.eracun.provider.model.ids.RemoteId;
import hr.com.port.ips.eracun.provider.model.money.Money;
import hr.com.port.ips.eracun.provider.model.money.Totals;
import hr.com.port.ips.eracun.provider.model.party.Party;
import hr.com.port.ips.eracun.provider.model.party.PartyId;
import hr.com.port.ips.eracun.provider.model.status.*;

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

public class MerModelMapper {

    private static final String OIB_SCHEME = "9934";

    public static DocumentRef toRef(String id, String created) {
        DocumentRef r = new DocumentRef();
        r.id = new RemoteId(id);
        r.insertedOn = created;
        return r;
    }

    // --- OUTBOX --------------------------------------------------------------

    public static DocumentHeader toHeader(OutboxDocumentHeader s, String ourOib) {
        DocumentHeader h = baseHeaderOut(s, ourOib);
        return h;
    }

    public static DocumentSnapshot toSnapshot(OutboxDocumentHeader s, String ourOib) {
        DocumentSnapshot d = baseSnapshotOut(s, ourOib);
        d.timeline = buildTimeline(s.getDocumentProcessStatusId(), s.getDocumentProcessStatusName(),
                                   s.getSent(), s.getDelivered(), s.getUpdated());
        return d;
    }

    private static DocumentHeader baseHeaderOut(OutboxDocumentHeader s, String ourOib) {
        DocumentHeader h = new DocumentHeader();
        h.id = new RemoteId(String.valueOf(s.getElectronicId()));
        h.key = outKey(s, ourOib);
        h.kind = "CreditNote".equalsIgnoreCase(s.getDocumentTypeName()) ? DocumentKind.CREDIT_NOTE : DocumentKind.INVOICE;
        h.issuedOn = s.getIssueDate();
        h.modifiedOn = s.getUpdated();
        h.totals = new Totals();
        h.totals.gross = new Money(); // iz MER headera nemamo bruto iznos → ostavi null.amount
        h.transportStatus = mapTransport(s.getStatusId(), s.getStatusName(), s.getSent(), s.getDelivered());
        h.businessStatus  = mapBusiness(s.getDocumentProcessStatusId(), s.getDocumentProcessStatusName());
        h.settlementStatus = mapSettlement(s.getDocumentProcessStatusId(), s.getDocumentProcessStatusName());
        return h;
    }

    private static DocumentSnapshot baseSnapshotOut(OutboxDocumentHeader s, String ourOib) {
        DocumentSnapshot d = new DocumentSnapshot();
        d.id = new RemoteId(String.valueOf(s.getElectronicId()));
        d.key = outKey(s, ourOib);
        d.kind = "CreditNote".equalsIgnoreCase(s.getDocumentTypeName()) ? DocumentKind.CREDIT_NOTE : DocumentKind.INVOICE;
        d.issuedOn = s.getIssueDate();
        d.modifiedOn = s.getUpdated();

        Party sup = new Party();
        sup.name = null; // naše ime nije u headeru; ako treba, može doći izvana
        sup.ids = new PartyId[] { new PartyId(OIB_SCHEME, ourOib) };
        d.supplier = sup;

        Party cust = new Party();
        cust.name = s.getRecipientBusinessName();
        cust.ids = new PartyId[] { new PartyId(OIB_SCHEME, s.getRecipientBusinessNumber()) };
        d.customer = cust;

        d.totals = new Totals();
        d.totals.gross = new Money(); // iz headera obično nema iznosa
        return d;
    }

    private static DocumentKey outKey(OutboxDocumentHeader s, String ourOib) {
        DocumentKey k = new DocumentKey();
        k.documentId = s.getDocumentNr();
        k.issueDate  = trimDate(s.getIssueDate());
        k.supplier   = new PartyId(OIB_SCHEME, ourOib);
        k.customer   = new PartyId(OIB_SCHEME, s.getRecipientBusinessNumber());
        return k;
    }

    // --- INBOX ---------------------------------------------------------------

    public static DocumentHeader toHeader(InboxDocumentHeader s, String ourOib) {
        DocumentHeader h = baseHeaderIn(s, ourOib);
        return h;
    }

    public static DocumentSnapshot toSnapshot(InboxDocumentHeader s, String ourOib) {
        DocumentSnapshot d = baseSnapshotIn(s, ourOib);
        d.timeline = buildTimeline(s.getDocumentProcessStatusId(), s.getDocumentProcessStatusName(),
                                   s.getSent(), s.getDelivered(), null);
        return d;
    }

    private static DocumentHeader baseHeaderIn(InboxDocumentHeader s, String ourOib) {
        DocumentHeader h = new DocumentHeader();
        h.id = new RemoteId(String.valueOf(s.ElectronicId));
        h.key = inKey(s, ourOib);
        h.kind = "CreditNote".equalsIgnoreCase(s.DocumentTypeName) ? DocumentKind.CREDIT_NOTE : DocumentKind.INVOICE;
        h.issuedOn = s.IssueDate;
        h.modifiedOn = s.Delivered != null ? s.Delivered : s.Sent;
        h.totals = new Totals();
        h.totals.gross = new Money();
        h.transportStatus = mapTransport(s.StatusId, s.StatusName, s.Sent, s.Delivered);
        h.businessStatus  = mapBusiness(s.DocumentProcessStatusId, s.DocumentProcessStatusName);
        h.settlementStatus = mapSettlement(s.DocumentProcessStatusId, s.DocumentProcessStatusName);
        return h;
    }

    private static DocumentSnapshot baseSnapshotIn(InboxDocumentHeader s, String ourOib) {
        DocumentSnapshot d = new DocumentSnapshot();
        d.id = new RemoteId(String.valueOf(s.ElectronicId));
        d.key = inKey(s, ourOib);
        d.kind = "CreditNote".equalsIgnoreCase(s.DocumentTypeName) ? DocumentKind.CREDIT_NOTE : DocumentKind.INVOICE;
        d.issuedOn = s.IssueDate;
        d.modifiedOn = s.Delivered != null ? s.Delivered : s.Sent;

        Party sup = new Party();
        sup.name = s.SenderBusinessName;
        sup.ids = new PartyId[] { new PartyId(OIB_SCHEME, s.SenderBusinessNumber) };
        d.supplier = sup;

        Party cust = new Party();
        cust.name = null;
        cust.ids = new PartyId[] { new PartyId(OIB_SCHEME, ourOib) };
        d.customer = cust;

        d.totals = new Totals();
        d.totals.gross = new Money();
        return d;
    }

    private static DocumentKey inKey(InboxDocumentHeader s, String ourOib) {
        DocumentKey k = new DocumentKey();
        k.documentId = s.DocumentNr;
        k.issueDate  = trimDate(s.IssueDate);
        k.supplier   = new PartyId(OIB_SCHEME, s.SenderBusinessNumber);
        k.customer   = new PartyId(OIB_SCHEME, ourOib);
        return k;
    }

    // --- helpers -------------------------------------------------------------

    public static DocumentBinary xml(String base64) {
        DocumentBinary b = new DocumentBinary();
        b.mime = "application/xml";
        b.contentBase64 = base64;
        return b;
    }

    private static String trimDate(String iso) {
        if (iso == null) return null;
        return iso.length() >= 10 ? iso.substring(0, 10) : iso;
    }

    private static TransportStatus mapTransport(Integer statusId, String statusName, String sent, String delivered) {
        if (delivered != null && !delivered.isEmpty()) return TransportStatus.DELIVERED;
        if (sent != null && !sent.isEmpty()) return TransportStatus.SENT;
        // fallback po nazivu/kodu
        String n = statusName == null ? "" : statusName.toUpperCase();
        if (n.contains("DELIVER")) return TransportStatus.DELIVERED;
        if (n.contains("SENT") || n.contains("POSLAN")) return TransportStatus.SENT;
        if (n.contains("FAIL") || n.contains("NEUSPJ")) return TransportStatus.FAILED;
        return TransportStatus.UNKNOWN;
    }

    private static BusinessStatus mapBusiness(Integer procId, String procName) {
        MerProcessStatus m = MerProcessStatus.fromId(procId);
        switch (m) {
            case APPROVED: return BusinessStatus.APPROVED;
            case REJECTED: return BusinessStatus.REJECTED;
            case RECEIVING_CONFIRMED:
            case RECEIVED: return BusinessStatus.RECEIVED;
            default: return BusinessStatus.UNKNOWN;
        }
    }

    private static SettlementStatus mapSettlement(Integer procId, String procName) {
        MerProcessStatus m = MerProcessStatus.fromId(procId);
        switch (m) {
            case PAYMENT_FULFILLED: return SettlementStatus.PAID;
            case PAYMENT_PARTIALLY_FULFILLED: return SettlementStatus.PARTIALLY_PAID;
            default: return SettlementStatus.UNPAID;
        }
    }

    private static TimelineEvent[] buildTimeline(Integer procId, String procName, String sent, String delivered, String updated) {
        List<TimelineEvent> ev = new ArrayList<>();
        if (sent != null) {
            TimelineEvent t = new TimelineEvent();
            t.when = sent; t.type = EventType.TRANSPORT_SENT; t.note = "Sent";
            ev.add(t);
        }
        if (delivered != null) {
            TimelineEvent t = new TimelineEvent();
            t.when = delivered; t.type = EventType.TRANSPORT_DELIVERED; t.note = "Delivered";
            ev.add(t);
        }
        if (updated != null) {
            TimelineEvent t = new TimelineEvent();
            t.when = updated; t.type = EventType.OTHER; t.note = "Updated";
            ev.add(t);
        }
        // Process status snapshot
        if (procId != null) {
            MerProcessStatus m = MerProcessStatus.fromId(procId);
            TimelineEvent t = new TimelineEvent();
            t.when = updated != null ? updated : delivered != null ? delivered : sent;
            switch (m) {
                case APPROVED: t.type = EventType.BUSINESS_APPROVED; break;
                case REJECTED: t.type = EventType.BUSINESS_REJECTED; break;
                case PAYMENT_FULFILLED: t.type = EventType.SETTLEMENT_PAID; break;
                case PAYMENT_PARTIALLY_FULFILLED: t.type = EventType.SETTLEMENT_PARTIALLY_PAID; break;
                case RECEIVED: t.type = EventType.BUSINESS_RECEIVED; break;
                default: t.type = EventType.OTHER;
            }
            t.note = (procName != null ? procName : m.name());
            ev.add(t);
        }
        return ev.toArray(new TimelineEvent[0]);
    }
}