package leigh.chain.msg;

import leigh.mecha.json.JSONObject;
import leigh.mecha.log.MechaLogger;
import leigh.mecha.log.MechaLoggerFactory;
import org.java_websocket.util.Base64;
import org.web3j.crypto.CipherException;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.Sign;
import org.web3j.crypto.WalletUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.SignatureException;
import java.util.Objects;

/**
 * This class represents a cryptographically signed message. The message contents themselves are arbitrary. The
 * message is signed using an Etherium wallet. The purpose for signing messages in this way is to provide attestation
 * off-chain which is traceable back to an Etherium wallet.
 * <p>
 * The contents may be binary, in which case they will be represented as a Base64 string in the envelope. The contents
 * may be a regular UTF8 string, including JSON, in which case they will be represented directly in the envelope. It
 * is up to the caller to know whether or not their content is suitable for string encoding or requires binary encoding,
 * since as a matter of performance, this class does not inspect the contents other than to generate the cryptographic
 * signature.
 *
 * @author Alex Leigh
 */
public class SignedMessage {
    private static final MechaLogger logger = MechaLoggerFactory.getLogger(SignedMessage.class);
    private String content;
    private Sign.SignatureData signatureData;

    public void sign(String fn, String password) throws CipherException, IOException, SignatureException {
        Credentials credentials = WalletUtils.loadCredentials(password, fn);
        signatureData = Sign.signMessage(content.getBytes(StandardCharsets.UTF_8),
                credentials.getEcKeyPair());
    }

    public void recoverKey() {
        /*
        Sign.SignatureData recovered = new Sign.SignatureData(signatureData.getV(),
                signatureData.getR(), signatureData.getS());

        BigInteger recovKey = Sign.signedMessageToKey(content, recovered);
        logger.info("Recovered key: {}", Keys.getAddress(recovKey));
         */
    }

    /**
     * Return a new JSON envelope representing the signature of this message along with the message itself. If the
     * message has not been signed an exception is thrown.
     */
    public JSONObject getJSONEnvelope() {
        if (signatureData == null) throw new IllegalStateException("Message has not been signed.");
        JSONObject jo = new JSONObject();
        jo.put("m", new String(content));

        jo.put("v", Base64.encodeBytes(signatureData.getV()));
        jo.put("r", Base64.encodeBytes(signatureData.getR()));
        jo.put("s", Base64.encodeBytes(signatureData.getS()));
        return jo;
    }


    /**
     * Set the content to the provided string data. If a signature had previously been created, it will be removed,
     * so <code>sign()</code> will need to be called again.
     */
    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        SignedMessage that = (SignedMessage) o;
        return Objects.equals(content, that.content);
    }

    public String getContent() {
        return content;
    }

    @Override
    public int hashCode() {
        return Objects.hash(content);
    }

    @Override
    public String toString() {
        return "SignedMessage{" +
                "content='" + content + '\'' +
                ", signatureData=" + signatureData +
                '}';
    }
}
