package cz.muni.ics.oidc.server.ga4gh;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.Payload;
import com.nimbusds.jwt.JWTParser;
import com.nimbusds.jwt.SignedJWT;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

@Slf4j
public class Ga4ghConsentUtils {

    public static class PassportVisa {
        private final String jwt;
        private String prettyPayload;
        private String sub;
        private String iss;
        private String type;
        private String value;

        public PassportVisa(String jwt) {
            this.jwt = jwt;
        }

        public String getJwt() {
            return jwt;
        }

        void setPrettyPayload(String prettyPayload) {
            this.prettyPayload = prettyPayload;
        }

        public String getPrettyString() {
            return prettyPayload;
        }

        @Override
        public String toString() {
            return "PassportVisa{" +
                    "  type=" + type +
                    ", sub=" + sub +
                    ", iss=" + iss +
                    ", value=" + value +
                    '}';
        }

        public void setSub(String sub) {
            this.sub = sub;
        }

        public String getSub() {
            return sub;
        }

        public void setIss(String iss) {
            this.iss = iss;
        }

        public String getIss() {
            return iss;
        }

        public void setType(String type) {
            this.type = type;
        }

        public String getType() {
            return type;
        }

        public void setValue(String value) {
            this.value = value;
        }

        public String getValue() {
            return value;
        }
    }

    public static PassportVisa parseAndVerifyVisa(String jwtString) {
        PassportVisa visa = new PassportVisa(jwtString);
        try {
            SignedJWT signedJWT = (SignedJWT) JWTParser.parse(jwtString);
            processPayload(visa, signedJWT.getPayload());
        } catch (Exception ex) {
            log.error("visa " + jwtString + " cannot be parsed and verified", ex);
        }
        return visa;
    }

    private static final ObjectMapper JSON_MAPPER = new ObjectMapper();

    private static void processPayload(PassportVisa visa, Payload payload) throws IOException {
        JsonNode doc = JSON_MAPPER.readValue(payload.toString(), JsonNode.class);
        checkVisaKey(visa, doc, "sub");
        checkVisaKey(visa, doc, "exp");
        checkVisaKey(visa, doc, "iss");
        JsonNode visaV1 = doc.path("ga4gh_visa_v1");
        checkVisaKey(visa, visaV1, "type");
        checkVisaKey(visa, visaV1, "asserted");
        checkVisaKey(visa, visaV1, "value");
        checkVisaKey(visa, visaV1, "source");
        checkVisaKey(visa, visaV1, "by");
        long exp = doc.get("exp").asLong();
        if (exp < Instant.now().getEpochSecond()) {
            return;
        }
        visa.setPrettyPayload(
                visaV1.get("type").asText() + ":  \"" + visaV1.get("value").asText() + "\" asserted " + isoDate(visaV1.get("asserted").asLong())
        );
    }

    private static void checkVisaKey(PassportVisa visa, JsonNode jsonNode, String key) {
        if (!jsonNode.path(key).isMissingNode()) {
            switch (key) {
                case "sub":
                    visa.setSub(jsonNode.path(key).asText());
                    break;
                case "iss":
                    visa.setIss(jsonNode.path(key).asText());
                    break;
                case "type":
                    visa.setType(jsonNode.path(key).asText());
                    break;
                case "value":
                    visa.setValue(jsonNode.path(key).asText());
                    break;
                default:
            }
        }
    }

    private static String isoDate(long linuxTime) {
        return DateTimeFormatter.ISO_LOCAL_DATE.format(ZonedDateTime.ofInstant(Instant.ofEpochSecond(linuxTime), ZoneId.systemDefault()));
    }

}
