import { AbstractAccountKeyring, AbstractSelfAccountKeyring } from "../../keyring/account_base";
import { AbstractCryptoConfig } from "../base";
import { AccountMasterDeriveKeyV1 } from "../common";
import { AccountTrustConfigDumpArgs, AccountTrustConfigLoadArgs } from "./args";
import { makeInfoByteArray, ValueError } from "../../utility";

import { base64 } from "rfc4648";
import { MissingKey } from "../../keyring/base";

/**
 * Base class for trust-related errors.
    When this is being handled instead of a more specific exception, the trust must be treated as compromised!
 */
export class TrustError extends Error{ 
}

/**
 * Some error happened, the peer must not be trusted!
 */
export class TrustCompromised extends TrustError{ 
}

/**
 * Trust is not of preferred key, but still fine.
 */
export class TrustIsStale extends TrustError{ 
}


/**
 * Raised when the current trust fingerprint is valid, but there is a newer trust root which should be stored as
    a fingerprint. The system can automatically upgrade the trust if the new trust root is signed with the old one
    and not only the other way around.
    NOTE: Even if auto-upgrade is possible after a while (when the old trust root is sufficiently obsolete) it will
    not be performed anymore and manual confirmation will be necessary.
 */
export class TrustNeedsUpgrade extends TrustError{ 
}


/**
 * The trust of the account builds on chained signatures of root trusts (by upgrades) and signatures of other keys.
 */
export abstract class AbstractAccountTrustConfig extends AbstractCryptoConfig<AccountTrustConfigLoadArgs, AccountTrustConfigDumpArgs>{

    public load(args: AccountTrustConfigLoadArgs): Promise<Uint8Array>{
        try{
            return this.derive_key(args.self_kr).then((derive_key)=>{
                return this._decrypt(args.cipher, derive_key).then((fingerprint)=>{
                    
                    
                    let could_upgrade = false;

                    let checkFingerprint = (index)=>{
                        if (index < AbstractAccountKeyring.TRUST_KEY_VERSIONS.length){
                            let key_version = AbstractAccountKeyring.TRUST_KEY_VERSIONS[index];

                            let trust_root;
                            try{
                                trust_root = args.peer_kr.get_public_key(key_version);
                            }
                            catch(e){
                                // TODO MissingKey check
                                if (e instanceof MissingKey){
                                    return checkFingerprint(index+1)
                                }
                                else{
                                    throw e;
                                }
                            }

                            // NOTE: The naive comparison is not a problem here, because the authenticated decryption protects
                            // us from guessing&timing attacks.
                            return this._get_fingerprint(trust_root, args.self_kr).then((getFingerprint)=>{
                                if (base64.stringify(getFingerprint) == base64.stringify(fingerprint)){
                                    
                                    if (key_version == AbstractAccountKeyring.TRUST_KEY_VERSIONS[0]){
                                        return trust_root; // RETURN
                                    }
                                    if (could_upgrade){
                                        throw new TrustNeedsUpgrade();
                                    }
                                    throw new TrustIsStale();
                                }

                                could_upgrade = true;
                                
                                return checkFingerprint(index+1)
                            })
                        }
                        else{
                            throw new TrustCompromised();
                        }

                        

                    }

                    
                    // Check the fingerprint from highest to lowest trust root.
                    return checkFingerprint(0); 
                    
        
                })
            })
        }
        catch(e){
            if (e instanceof ValueError){
                throw new TrustCompromised();
            }
            throw e;
        }
        
        
    }

    public dump(args: AccountTrustConfigDumpArgs): Promise<Uint8Array>{
        if (!AbstractAccountKeyring.TRUST_KEY_VERSIONS.includes(args.key_version)){
            throw "Invalid key-version for trust fingerprint";
        }
        return this.derive_key(args.self_kr).then((derive_key)=>{
            return this._get_fingerprint(args.peer_kr.get_public_key(args.key_version), args.self_kr).then((fingerprint)=>{
                return this._encrypt(
                    fingerprint,
                    derive_key
                )
            })
            
        })
        
    }

    public derive_key(self_kr: AbstractSelfAccountKeyring): Promise<Uint8Array>{
        return AccountMasterDeriveKeyV1.derive_key(self_kr, makeInfoByteArray(["account-trust-", this.version]))
    }

    protected abstract _decrypt(cipher: Uint8Array, key: Uint8Array): Promise<Uint8Array>;

    protected abstract _encrypt(plain: Uint8Array, key: Uint8Array): Promise<Uint8Array>;

    /**
     * The fingerprint for a peer account is made from one of their trust roots. Including the peer account's id
        is not necessary, because even if a malicious server orchestrates a fake account masquerading with the same
        keys as a trusted one it cannot gain any information or access to data unless it knows the secret keys. If
        it knows the secret keys there is no need to orchestrate any attack because it can decrypt all data.
        The self keyring is provided here to fingerprint the peer-root-key in an exclusive and secret way. It normally
        would not be an issue because of the encryption of the fingerprint, but this will completely prevent a
        malicious server from doing chosen-ciphertext attacks against the trust-store's encryption.
     */
    protected abstract _get_fingerprint(peer_root_key: Uint8Array, self_kr: AbstractSelfAccountKeyring): Promise<Uint8Array>;
}