import { AbstractAccountKeyring, AbstractPeerAccountKeyring, AbstractSelfAccountKeyring } from "./account_base";
import { ACCOUNT_SIGNATURE_CRYPTO_CONTEXT } from '../context/account_signature/__init__'
import { AccountSignatureConfigLoadArgs } from "../context/account_signature/args";
import { ACCOUNT_TRUST_CRYPTO_CONTEXT } from '../context/account_trust/__init__'
import { AccountTrustConfigLoadArgs } from "../context/account_trust/args";
import { TrustIsStale, TrustNeedsUpgrade, TrustError } from "../context/account_trust/common";
import { UnknownCryptoConfigVersion } from "../context/base";
import { base64 } from "rfc4648";

/**
 * A peer account's cryptographic primitives are their public keys for encryption and signature verification.
 */
export class PeerAccountKeyring extends AbstractPeerAccountKeyring{


    /**
     * Loading a peer account's keyring requires checking the signatures of all keys by the trust root and
        verifying the trust-fingerprint of the root if present.
        After new keys and signing roots have been introduced the old ones can become optional.
        The caller and all codes must check the trusted state before using the keyring.

     */
    public static load(
        account_id: string,
        curve25519_public: Uint8Array,
        curve25519_public_sig: Uint8Array,
        ed25519_public: Uint8Array,
        trust_fingerprint?: Uint8Array,
        self_kr?: AbstractSelfAccountKeyring,
        stored_tofu_fingerprints?: Object): Promise<PeerAccountKeyring>{
        
        let obj = {};
        obj[PeerAccountKeyring.CURVE25519] = curve25519_public;
        obj[PeerAccountKeyring.ED25519] = ed25519_public;

        let peer_kr = new PeerAccountKeyring(account_id, obj)

        // Signatures of all present keys are required regardless of trust.
        return ACCOUNT_SIGNATURE_CRYPTO_CONTEXT.load(
            new AccountSignatureConfigLoadArgs(curve25519_public_sig, peer_kr, curve25519_public)
        ).then((ctx)=>{
            // Check the fingerprint of the root signing key.
            if (trust_fingerprint){
                // If there is an explicit identity-trust fingerprint, the TOFU fingerprints are irrelevant.
                if (!self_kr){
                    throw "Self keyring is required for checking trust fingerprint";
                }
                peer_kr.update(trust_fingerprint, self_kr); 

                return peer_kr;
            }
            else{
                // Generate the TOFU fingerprints for validation and storage.
                
                return peer_kr.create_tofu_fingerprints().then(()=>{
                    if (stored_tofu_fingerprints){
                        for (let key_version in PeerAccountKeyring.TRUST_KEY_VERSIONS){
                            if (!stored_tofu_fingerprints.hasOwnProperty(key_version) || !peer_kr.tofu_fingerprints.includes(key_version)){
                                continue; // we can only check these if both exist
                            }
                            // NOTE: Naive comparison should not pose much threat here. Loading a keyring is initiated by the
                            // client and and attacker cannot measure when it "fails". (especially because it is cached)
                            if (base64.stringify(stored_tofu_fingerprints[key_version]) != base64.stringify(peer_kr.tofu_fingerprints[key_version])){
                                peer_kr.trusted = PeerAccountKeyring.TRUSTED_NOT;
                                break;
                            }
                        }
                    }

                    return peer_kr;
                })                
            }

        })

        
    }

    // Updating the trust state after loading shall only be done when a user explicitly verifies a peer.
    public update(trust_fingerprint: Uint8Array, self_kr: AbstractSelfAccountKeyring){
        try{
            ACCOUNT_TRUST_CRYPTO_CONTEXT.load(new AccountTrustConfigLoadArgs(trust_fingerprint, self_kr, this))
        
            this.trusted = AbstractAccountKeyring.TRUSTED_FULLY;
        }
        catch(e){
            if (e instanceof TrustIsStale){
                this.trusted = AbstractAccountKeyring.TRUSTED_STALE;
            }
            else if (e instanceof TrustNeedsUpgrade){
                // The upgrade may need to be done manually if the fingerprint is for an ancient trust root.
                this.trusted = AbstractAccountKeyring.TRUSTED_REQUIRES_UPGRADE;
            }
            else if (e instanceof TrustError || e instanceof UnknownCryptoConfigVersion){
                // When the verification fails in an "expected" way the keyring can be constructed and examined
                // to see what the problem is, but must not be used!
                this.trusted = AbstractAccountKeyring.TRUSTED_NOT;
            }
        }
    }

    

    /**
     * Loading an anonymous peer account's keyring has no checks for authenticity because the peer is unknown.
        The caller code must keep in mind that a keyring is for an anonymous peer and work accordingly.
     */
    public load_anonymous(curve25519_public: Uint8Array){
        return new PeerAccountKeyring(AbstractAccountKeyring.ANONYMOUS_ID, [PeerAccountKeyring.CURVE25519, curve25519_public])
    }

}