import { AESCipherSIVNonce96 } from "../../base/cipher/aes";
import { AbstractSelfAccountKeyring } from "../../keyring/account_base";
import { PeerAccountKeyring } from "../../keyring/account_peer";
import { AbstractCryptoConfig } from "../base";
import { AccountPairDeriveKeyV1, TS_WINDOW_V1 } from "../common";
import { DialogueConfigDumpArgs, DialogueConfigLoadArgs } from "./args";
import { makeInfoByteArray, ValueError } from "../../utility";

/**
 * Dialogue message encryption: AES-SIV
        KEY: 32B from curve25519 scalar_mult -> 64B HKDF by context + current time-slice-window
        NONCE: 12B random

    Key rotation by time is not necessary here by the key usage safety limits, but we do it nevertheless because a
    timestamp is always available and we can limit the blast radius of a single key leak (even if not really relevant).
    NOTE: Large LRU is useful here for pair and given time-window.
 */
export class DialogueConfigV1 extends AbstractCryptoConfig<DialogueConfigLoadArgs, DialogueConfigDumpArgs>{

    public static TS_WINDOW = TS_WINDOW_V1;

    constructor(never_dump: boolean = false){
        super(1, never_dump);
    }

    public load(args: DialogueConfigLoadArgs): Promise<Uint8Array>{
        return new Promise((resolve, reject)=>{
            
            let error;
            
            let cipher = args.cipher.slice(this.version.byteLength);
            let timestampArr = DialogueConfigV1.TS_WINDOW.windows(args.timestamp);

            let _load = (timestamp_index)=>{
                
                if (timestamp_index >= timestampArr.length){
                    reject(error)
                    return;
                }
                
                let timestamp_window = timestampArr[timestamp_index];
                
                this.derive_key(args.self_kr, args.peer_kr, timestamp_window).then((derive_key)=>{
                    
                    AESCipherSIVNonce96.decrypt(
                        cipher, 
                        derive_key
                    ).then((res)=>{
                        resolve(res);
                        return;
                    }).catch((err)=>{
                        if (err instanceof ValueError){
                            error = err;
                            _load(timestamp_index+1);
                        }
                        else{
                            reject(err);
                        }
                    })
                }).catch(reject)

            }

            _load(0);

        });
        
    }

    public dump(args: DialogueConfigDumpArgs): Promise<Uint8Array>{
        return this.derive_key(args.self_kr, args.peer_kr, DialogueConfigV1.TS_WINDOW.window(args.timestamp)).then((derive_key)=>{
            return AESCipherSIVNonce96.encrypt(
                args.plain,
                derive_key,
                undefined,
                undefined,
                this.version
            )
        })        
    }

    public derive_key(self_kr: AbstractSelfAccountKeyring, peer_kr: PeerAccountKeyring, ts_window: Uint8Array){
        return AccountPairDeriveKeyV1.derive_key(self_kr, peer_kr, makeInfoByteArray(["dialogue-", this.version, "-", ts_window]))
        
    }
}