package org.jboss.resteasy.security.doseta;

import java.security.PublicKey;
import java.security.SignatureException;
import java.util.HashMap;
import java.util.Map;

import jakarta.ws.rs.core.MultivaluedMap;

import org.jboss.resteasy.security.doseta.i18n.Messages;

/**
 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
 * @version $Revision: 1 $
 */
public class Verification {
    protected PublicKey key;
    protected KeyRepository repository;
    protected String algorithm = DKIMSignature.DEFAULT_ALGORITHM;
    protected Map<String, String> requiredAttributes = new HashMap<String, String>();
    protected String identifierName;
    protected String identifierValue;
    protected boolean staleCheck;
    protected boolean ignoreExpiration;
    protected int staleSeconds;
    protected int staleMinutes;
    protected int staleHours;
    protected int staleDays;
    protected int staleMonths;
    protected int staleYears;
    protected boolean bodyHashRequired = true;

    public Verification() {
    }

    public Verification(final PublicKey key) {
        this.key = key;
    }

    public Verification(final KeyRepository repository) {
        this.repository = repository;
    }

    public boolean isBodyHashRequired() {
        return bodyHashRequired;
    }

    public void setBodyHashRequired(boolean bodyHashRequired) {
        this.bodyHashRequired = bodyHashRequired;
    }

    public String getIdentifierName() {
        return identifierName;
    }

    public void setIdentifierName(String identifierName) {
        this.identifierName = identifierName;
    }

    public String getIdentifierValue() {
        return identifierValue;
    }

    public void setIdentifierValue(String identifierValue) {
        this.identifierValue = identifierValue;
    }

    public boolean isIgnoreExpiration() {
        return ignoreExpiration;
    }

    public void setIgnoreExpiration(boolean ignoreExpiration) {
        this.ignoreExpiration = ignoreExpiration;
    }

    public boolean isStaleCheck() {
        return staleCheck;
    }

    public void setStaleCheck(boolean staleCheck) {
        this.staleCheck = staleCheck;
    }

    public Map<String, String> getRequiredAttributes() {
        return requiredAttributes;
    }

    public String getAlgorithm() {
        return algorithm;
    }

    public void setAlgorithm(String algorithm) {
        this.algorithm = algorithm;
    }

    public PublicKey getKey() {
        return key;
    }

    public void setKey(PublicKey key) {
        this.key = key;
    }

    public KeyRepository getRepository() {
        return repository;
    }

    public void setRepository(KeyRepository repository) {
        this.repository = repository;
    }

    public int getStaleSeconds() {
        return staleSeconds;
    }

    public void setStaleSeconds(int staleSeconds) {
        this.staleSeconds = staleSeconds;
    }

    public int getStaleMinutes() {
        return staleMinutes;
    }

    public void setStaleMinutes(int staleMinutes) {
        this.staleMinutes = staleMinutes;
    }

    public int getStaleHours() {
        return staleHours;
    }

    public void setStaleHours(int staleHours) {
        this.staleHours = staleHours;
    }

    public int getStaleDays() {
        return staleDays;
    }

    public void setStaleDays(int staleDays) {
        this.staleDays = staleDays;
    }

    public int getStaleMonths() {
        return staleMonths;
    }

    public void setStaleMonths(int staleMonths) {
        this.staleMonths = staleMonths;
    }

    public int getStaleYears() {
        return staleYears;
    }

    public void setStaleYears(int staleYears) {
        this.staleYears = staleYears;
    }

    /**
     * Headers can be a {@literal Map<String, Object> or a Map<String, List<Object>>}. This gives some compatibility with
     * JAX-RS's MultivaluedMap. If a map of lists, every value of each header duplicate will be added.
     *
     * @param signature signature
     * @param headers   headers map
     * @param body      body
     * @param publicKey public key
     * @return map of validated headers and their values
     * @throws SignatureException if verification fails
     */
    public MultivaluedMap<String, String> verify(DKIMSignature signature, Map headers, byte[] body, PublicKey publicKey)
            throws SignatureException {
        if (publicKey == null)
            publicKey = key;
        if (publicKey == null)
            throw new SignatureException(Messages.MESSAGES.publicKeyIsNull());

        MultivaluedMap<String, String> verifiedHeaders = signature.verify(bodyHashRequired, headers, body, publicKey);

        if (isIgnoreExpiration() == false) {
            if (signature.isExpired()) {
                throw new SignatureException(Messages.MESSAGES.signatureExpired());
            }
        }
        if (isStaleCheck()) {
            if (signature.isStale(getStaleSeconds(),
                    getStaleMinutes(),
                    getStaleHours(),
                    getStaleDays(),
                    getStaleMonths(),
                    getStaleYears())) {
                throw new SignatureException(Messages.MESSAGES.signatureIsStale());
            }
        }

        for (Map.Entry<String, String> required : getRequiredAttributes().entrySet()) {
            String value = signature.getAttributes().get(required.getKey());
            if (!value.equals(required.getValue())) {
                throw new SignatureException(Messages.MESSAGES.expectedValue(required.getValue(), value, required.getKey()));
            }
        }

        return verifiedHeaders;

    }
}
