package security;

import com.alibaba.fastjson.JSON;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import org.apache.http.client.methods.HttpGet;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter;
import api.wsit.product_request.execute.ApiSchema;
import org.json.JSONObject;

import javax.xml.bind.DatatypeConverter;
import java.io.File;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import static config.ClientConfig.*;
import static security.MessageLevel.encrypt;
import static security.MessageLevel.encryptAndSign;
import static security.PemUtils.getPrivateKey;
import static security.PemUtils.parsePEMFile;

public class AuthUtility {

    public static Map<String, String> generateAuthTokens(String httpMethod, String apiSchema, String clientProfileId, String onBehalfOf, Map<String, Object> additionalTokenClaims, String requestPayload, PGPPublicKey bankPgpPublicKey, PGPPrivateKey clientPgpPrivateKey, boolean xHsbcCryptoSignature) {

        try {
            System.out.println("  #a. Using HashMap to build token structure...");
            HashMap<String, String> token = new HashMap<String, String>();
            // header
            Map<String, Object> headers = new HashMap<String, Object>();
            headers.put("kid", Long.toHexString(clientPgpPrivateKey.getKeyID()).toUpperCase());
            headers.put("typ", "JWT");
            headers.put("ver", "1.0");
            headers.put("alg", TOKEN_ALG);

            // claims
            PrivateKey clientRsaPrivateKey = new JcaPGPKeyConverter().getPrivateKey(clientPgpPrivateKey);
            Map<String, Object> claims = new HashMap<String, Object>();
            claims.put("jti", UUID.randomUUID().toString());
            claims.put("iat", System.currentTimeMillis() / 1000);
            claims.put("sub", clientProfileId); //API Profile ID Bank Assigned to you during on-boarding
            if (apiSchema == (ApiSchema.Edge.SCHEMA_NAME)) {
                claims.put("aud", "baas");
                // Obo, optional, only used when an API request is send on behalf of an End Customer
                if (onBehalfOf != null) {
                    Map<String, Object> obo = new HashMap<String, Object>();
                    obo.put("sub", onBehalfOf); //End Customer ID, provided by your platform during on-boarding
                    claims.put("obo", obo);
                }
            } else if (apiSchema == (ApiSchema.Gtrf.SCHEMA_NAME)) {
                claims.put("aud", "GTRF.MKT");
            }
            //if (requestPayload != null) {
            if (httpMethod != HttpGet.METHOD_NAME) {
                String payloadHash = DatatypeConverter.printHexBinary(MessageDigest.getInstance(TOKEN_PAYLOAD_HASH_ALG).digest(requestPayload.getBytes()))
                        .toLowerCase();
                claims.put("payload_hash", payloadHash);
                claims.put("payload_hash_alg", clientRsaPrivateKey.getAlgorithm() + TOKEN_PAYLOAD_HASH_ALG.replace("-", ""));
            }
            // print token object
            printAuthToken(headers, claims);
            System.out.println("  #b. Building JWT and Signed by: " + clientRsaPrivateKey.toString());
            JwtBuilder authorizationBuilder = Jwts.builder().setHeader(headers).setClaims(claims).signWith(clientRsaPrivateKey, SIGNATURE_ALG);
            //System.out.println("  #b. Building JWT and Signed by: " + Base64.getEncoder().encodeToString(pem.getEncoded()));
            //JwtBuilder jwtBuilder = Jwts.builder().setHeader(headers).setClaims(claims).signWith(pem, SIGNATURE_ALG);
            String authorization = authorizationBuilder.compact();
            System.out.println("  #c. Generated Authorization Token:\nJWS " + authorization);

            if (apiSchema == (ApiSchema.Edge.SCHEMA_NAME)) {
                Map<String, Object> xHsbcTradeFinanceClaims = new HashMap<String, Object>() {
                    {
                        put("jti", claims.get("jti"));
                        put("iat", claims.get("iat"));
                        put("sub", claims.get("sub"));
                        put("aud", claims.get("aud"));
                        if (claims.containsKey("obo")) {
                            put("obo", claims.get("obo"));
                        }
                    }
                };

                for (Map.Entry<String, Object> entry : additionalTokenClaims.entrySet()) {
                    if (entry.getKey() == "claimsNoEncrypt") {
                        Map<String, Object> xHsbcTradeFinanceClaimsNoEncrypt = (Map<String, Object>) entry.getValue();
                        for (Map.Entry<String, Object> innerEntry : xHsbcTradeFinanceClaimsNoEncrypt.entrySet()) {
                            xHsbcTradeFinanceClaims.put(innerEntry.getKey(), innerEntry.getValue());
                        }
                    }
                    if (entry.getKey() == "claimsToEncrypt") {
                        Map<String, Object> xHsbcTradeFinanceClaimsToEncrypt = (Map<String, Object>) entry.getValue();
                        for (Map.Entry<String, Object> innerEntry : xHsbcTradeFinanceClaimsToEncrypt.entrySet()) {
                            System.out.println(innerEntry.getValue().toString());
                            if (xHsbcCryptoSignature) {
                                xHsbcTradeFinanceClaims.put(innerEntry.getKey(), encryptAndSign(innerEntry.getValue().toString(), bankPgpPublicKey, clientPgpPrivateKey));
                            }
                            else {
                                xHsbcTradeFinanceClaims.put(innerEntry.getKey(), encrypt(innerEntry.getValue().toString(), bankPgpPublicKey));
                            }

                        }
                    }
                }
                printAuthToken(headers, xHsbcTradeFinanceClaims);
                JwtBuilder xHsbcTradeFinanceBuilder = Jwts.builder().setHeader(headers).setClaims(xHsbcTradeFinanceClaims).signWith(clientRsaPrivateKey, SIGNATURE_ALG);
                String xHsbcTradeFinance = xHsbcTradeFinanceBuilder.compact();
                System.out.println("  #c. Generated X-HSBC-Trade-Finance-Token:\nJWS " + xHsbcTradeFinance);
                token.put("xHsbcTradeFinance", xHsbcTradeFinance);
            }
            token.put("authorization", authorization);

            return token;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (PGPException e) {
            e.printStackTrace();
        }

        return null;
    }

    public static void printAuthToken(Map<String, Object> header, Map<String, Object> claims) {
        System.out.println("  #a.1. Token Headers:");
        for (Map.Entry<String, Object> entry : header.entrySet()) {
            System.out.println("         " + entry.getKey() + ":" + entry.getValue());
        }
        System.out.println("  #a.2. Token Claims:");
        for (Map.Entry<String, Object> entry : claims.entrySet()) {
            System.out.println("         " + entry.getKey() + ":" + entry.getValue());
        }
    }

}
