//
//  MFV3Serializers.swift
//  MyfatoorahReactnative
//
//  The V3 SDK result models are Decodable-only (and live in another module), so
//  Swift can't synthesize Encodable for them here. This file hand-serializes each
//  model into a JSON string with PascalCase keys matching the JS interfaces in
//  src/MFModels.tsx. Result enums become `type`-tagged objects.
//

import Foundation
import MFSDK

enum MFV3Serializers {

    // MARK: - Public entry points

    static func jsonString(from session: MFV3SessionData) -> String {
        return json(sessionDict(session))
    }

    static func jsonString(from result: MFV3PaymentResult) -> String {
        return json(paymentResultDict(result))
    }

    static func jsonString(from result: MFV3VerifyResult) -> String {
        return json(verifyResultDict(result))
    }

    // MARK: - Result enums

    private static func paymentResultDict(_ result: MFV3PaymentResult) -> [String: Any] {
        switch result {
        case .completed(let details):
            return ["type": "completed", "details": paymentDetailsDict(details)]
        case .cardCollected(let sessionId, let card):
            return prune(["type": "cardCollected", "sessionId": sessionId, "card": cardDetailsDict(card)])
        case .pendingBackendResolution(let paymentData, let redirectionUrl, let paymentId):
            return prune([
                "type": "pendingBackendResolution",
                "paymentData": paymentData,
                "redirectionUrl": redirectionUrl,
                "paymentId": paymentId
            ])
        @unknown default:
            return ["type": "unknown"]
        }
    }

    private static func verifyResultDict(_ result: MFV3VerifyResult) -> [String: Any] {
        switch result {
        case .verified(let session):
            return ["type": "verified", "session": getSessionDataDict(session)]
        case .pendingBackendResolution(let sessionId, let redirectionUrl):
            return prune(["type": "pendingBackendResolution", "sessionId": sessionId, "redirectionUrl": redirectionUrl])
        @unknown default:
            return ["type": "unknown"]
        }
    }

    // MARK: - Session

    private static func sessionDict(_ s: MFV3SessionData) -> [String: Any] {
        return prune([
            "SessionId": s.sessionId,
            "SessionExpiry": s.sessionExpiry,
            "EncryptionKey": s.encryptionKey,
            "OperationType": s.operationType,
            "Order": s.order.map(orderDict),
            "Customer": s.customer.map(sessionCustomerDict)
        ])
    }

    private static func orderDict(_ o: MFV3Order) -> [String: Any] {
        return prune([
            "Amount": o.amount as NSDecimalNumber,
            "Currency": o.currency,
            "ExternalIdentifier": o.externalIdentifier
        ])
    }

    private static func sessionCustomerDict(_ c: MFV3SessionCustomer) -> [String: Any] {
        return prune([
            "Reference": c.reference,
            "Cards": c.cards?.map(savedCardDict)
        ])
    }

    private static func savedCardDict(_ c: MFV3SavedCard) -> [String: Any] {
        return prune([
            "Is3DSVerified": c.is3DSVerified,
            "Token": c.token,
            "Number": c.number,
            "Brand": c.brand,
            "TokenType": c.tokenType,
            "NameOnCard": c.nameOnCard,
            "ExpiryMonth": c.expiryMonth,
            "ExpiryYear": c.expiryYear
        ])
    }

    // MARK: - Card (COLLECT_DETAILS)

    private static func cardDetailsDict(_ c: MFV3CardDetails) -> [String: Any] {
        return prune([
            "Brand": c.brand,
            "PanHash": c.panHash,
            "Token": c.token,
            "Number": c.number,
            "NameOnCard": c.nameOnCard,
            "ExpiryYear": c.expiryYear,
            "ExpiryMonth": c.expiryMonth,
            "Issuer": c.issuer,
            "IssuerCountry": c.issuerCountry,
            "FundingMethod": c.fundingMethod,
            "ProductName": c.productName
        ])
    }

    // MARK: - Payment details (COMPLETE_PAYMENT)

    private static func paymentDetailsDict(_ d: MFV3PaymentDetails) -> [String: Any] {
        return prune([
            "Invoice": d.invoice.map(invoiceDict),
            "Transaction": d.transaction.map(transactionDict),
            "Customer": d.customer.map(paymentCustomerDict),
            "Amount": d.amount.map(amountDict),
            "Suppliers": d.suppliers?.map(supplierDict)
        ])
    }

    private static func invoiceDict(_ i: MFV3Invoice) -> [String: Any] {
        return prune([
            "Id": i.id,
            "Status": i.status,
            "Reference": i.reference,
            "CreationDate": i.creationDate,
            "ExpirationDate": i.expirationDate,
            "ExternalIdentifier": i.externalIdentifier,
            "UserDefinedField": i.userDefinedField,
            "MetaData": i.metaData.map(metaDataDict)
        ])
    }

    private static func metaDataDict(_ m: MFV3ResultMetaData) -> [String: Any] {
        return prune(["UDF1": m.udf1, "UDF2": m.udf2, "UDF3": m.udf3, "UDF4": m.udf4, "UDF5": m.udf5])
    }

    private static func transactionDict(_ t: MFV3Transaction) -> [String: Any] {
        return prune([
            "Id": t.id,
            "Status": t.status,
            "PaymentMethod": t.paymentMethod,
            "PaymentId": t.paymentId,
            "ReferenceId": t.referenceId,
            "TrackId": t.trackId,
            "AuthorizationId": t.authorizationId,
            "TransactionDate": t.transactionDate,
            "ECI": t.eci,
            "IP": t.ip.map(ipDict),
            "Error": t.error.map(transactionErrorDict),
            "Card": t.card.map(transactionCardDict)
        ])
    }

    private static func ipDict(_ ip: MFV3IP) -> [String: Any] {
        return prune(["Address": ip.address, "Country": ip.country])
    }

    private static func transactionErrorDict(_ e: MFV3TransactionError) -> [String: Any] {
        return prune(["Code": e.code, "Message": e.message])
    }

    private static func transactionCardDict(_ c: MFV3TransactionCard) -> [String: Any] {
        return prune([
            "NameOnCard": c.nameOnCard,
            "Number": c.number,
            "PanHash": c.panHash,
            "ExpiryMonth": c.expiryMonth,
            "ExpiryYear": c.expiryYear,
            "Brand": c.brand,
            "Issuer": c.issuer,
            "IssuerCountry": c.issuerCountry,
            "FundingMethod": c.fundingMethod,
            "Token": c.token
        ])
    }

    private static func paymentCustomerDict(_ c: MFV3PaymentCustomer) -> [String: Any] {
        return prune(["Name": c.name, "Mobile": c.mobile, "Email": c.email])
    }

    private static func amountDict(_ a: MFV3Amount) -> [String: Any] {
        return prune([
            "BaseCurrency": a.baseCurrency,
            "ValueInBaseCurrency": a.valueInBaseCurrency,
            "ServiceCharge": a.serviceCharge,
            "ServiceChargeVAT": a.serviceChargeVAT,
            "ReceivableAmount": a.receivableAmount,
            "DisplayCurrency": a.displayCurrency,
            "ValueInDisplayCurrency": a.valueInDisplayCurrency,
            "PayCurrency": a.payCurrency,
            "ValueInPayCurrency": a.valueInPayCurrency
        ])
    }

    private static func supplierDict(_ s: MFV3Supplier) -> [String: Any] {
        return prune([
            "Code": s.code,
            "Name": s.name,
            "InvoiceShare": s.invoiceShare,
            "ProposedShare": s.proposedShare,
            "DepositShare": s.depositShare
        ])
    }

    // MARK: - Verify (GET /v3/sessions)

    private static func getSessionDataDict(_ d: MFV3GetSessionData) -> [String: Any] {
        return prune([
            "SessionExpiry": d.sessionExpiry,
            "IsUsed": d.isUsed,
            "OperationType": d.operationType,
            "Order": d.order.map(orderDict),
            "CustomerReference": d.customerReference,
            "Card": d.card.map(verifyCardDict),
            "TransactionResult": d.transactionResult.map(paymentDetailsDict)
        ])
    }

    private static func verifyCardDict(_ c: MFV3VerifyCard) -> [String: Any] {
        return prune([
            "Number": c.number,
            "ExpiryMonth": c.expiryMonth,
            "ExpiryYear": c.expiryYear,
            "Brand": c.brand,
            "PanType": c.panType,
            "Issuer": c.issuer,
            "PanHash": c.panHash,
            "Token": c.token,
            "NameOnCard": c.nameOnCard,
            "IssuerCountry": c.issuerCountry,
            "FundingMethod": c.fundingMethod,
            "ProductName": c.productName,
            "IsValidCard": c.isValidCard,
            "Is3DSVerified": c.is3DSVerified
        ])
    }

    // MARK: - Helpers

    /// Drops nil values so optional fields are simply absent in the JSON.
    private static func prune(_ pairs: [String: Any?]) -> [String: Any] {
        var result: [String: Any] = [:]
        for (key, value) in pairs {
            if let value = value { result[key] = value }
        }
        return result
    }

    private static func json(_ dict: [String: Any]) -> String {
        guard let data = try? JSONSerialization.data(withJSONObject: dict, options: []),
              let string = String(data: data, encoding: .utf8) else {
            return "{}"
        }
        return string
    }
}
