/**
 *  @module     @magic.batua/points
 *  @overview   Defines the `Ledger` class that manages loyalty points logic across the
 *              application.
 *  
 *  @author     Animesh Mishra <hello@animesh.ltd>
 *  @copyright  © 2018 Animesh Ltd. All Rights Reserved.
 */

import * as Crypto                  from "crypto"
import * as Request                 from "request-promise-native"
import * as Points                  from "./Source/Points"
import { ObjectID }                 from "mongodb"
import { ExternalError }            from "@magic.batua/error"
import { ClientError }              from "@magic.batua/error"
import { Code }                     from "@magic.batua/error"
import { Transaction, InitTransaction }  from "./Source/Transaction"

/**
 *  The Points Ledger keeps track of a user's Magic Points, their status, their availability
 *  and other points related arithmetic.
 *  
 *  Magic Points are awarded to users as rewards for continued usage and word-of-mouth publicity. 
 *  The points are rewarded as following:
 * 
 *  -   600 points at signup. These points aren't redeemable until you've spent ₹1,000 or more on the 
 *      Magic Batua platform.
 * 
 *  -   400 points if somebody signs up using your referral code. These points become redeemable once
 *      you've amassed 10 or more referrals, and each of those referrals have spent ₹1,000 or more.
 * 
 *  -   1 point for mobile recharge. Redeemable immediately. If were referred by someone, their account 
 *      is credited with a point too.
 * 
 *  -   20 points for tuition fee payments. Redeemable immediately. If were referred by someone, their 
 *      account is credited with 20 points too.
 * 
 *  To manage the points logic, we make use of three buckets: Signup, Referrals and Available. As points
 *  earned through Signup and Referrals become redeemable they are emptied into the Available bucket. 
 *  Points earned through other activities such as mobile recharge or tuition fee payment are credited to
 *  the Available bucket straightaway.
 */
export class Ledger {
    /** Number of points available for redemption */
    public available: number = 0
    /** 
     *  Points earned at signup. Becomes redeemable once the account has spent 
     *  ₹1,000 or more on the Magic Batua platform.
     */
    public signup: number = 0

    /** 
     *  Points earned through referrals. Becomes redeemable once the account has accrued 
     *  10 or more referrals.
     */
    public referral: number = 0

    /** Number of points accured since the creation of account */
    public redeemable: number = 0

    /** Points that have expired. */
    public expired: number = 0

    /** Points that have been redeemed since the creation of account */
    public redeemed: number = 0

    /** Points that have been refunded */
    public refunded: number = 0

    /** Points transactions listing all the issuance, redemption and refund transactions. */
    public transactions = new Array<Transaction>()

    public constructor(transactions: Array<InitTransaction>) {
        for(var entry of transactions) {
            this.transactions.push(new Transaction(entry))
        }

        // Count all points
        this.count()
    }

    /**
     *  Goes over all point transactions one by one and separates all the points 
     *  in their respective baskets.
     *  
     *  @param transactions     An array of Points `Transaction`
     */
    private count() {
        this.signup     = 0
        this.referral   = 0
        this.expired    = 0
        this.redeemable = 0
        this.refunded   = 0
        this.redeemed   = 0

        for(var transaction of this.transactions) {
            switch(transaction.type) {
                case "Issue":
                    if(transaction.expiryDate!.valueOf() <= Date.now()) { this.expired += transaction.points; break }
                    if(transaction.notes == "Signup") { this.signup += transaction.points; break }
                    if(transaction.notes == "Referral") { this.referral += transaction.points; break }
                    else { this.redeemable += transaction.points; break }
                case "Redeem":
                    this.redeemed += transaction.points
                    break
                case "Refund":
                    this.refunded += transaction.points
            }
        }

        this.available = this.redeemable - this.redeemed
    }

    /** Issues a Magic Point for the given `reason` */
    public Issue(points: number, reason: string): Transaction {
        let transaction = new Transaction({
            points: points, 
            type: "Issue",
            notes: reason
        })

        // Add this transaction to the transactions array and count
        // the points again
        this.transactions.push(transaction)
        this.count()
        return transaction
    }

    /** Redeems `points` from the balance */
    public Redeem(points: number, reason: string): Transaction {
        if(points > this.available) {
            throw new ClientError(Code.BadRequest, 
                "Trying to redeem " + points + " points, but the account has " + this.redeemable + " redeemable points only.")
        }

        let transaction = new Transaction({
            points: points,
            type: "Redeem",
            notes: reason
        })

        // Add this transaction to the transactions array and count
        // the points again
        this.transactions.push(transaction)
        this.count()
        return transaction
    }

    /**
     *  This method will be called by the `Account` module when the condition for
     *  unlocking signup bonus is met.
     *  To move points from Signup bucket `this.signup` to Redeemable bucket `this.redeemable`,
     *  we change the `notes` property of signup points issuance from "Signup" to 
     *  "Signup Points become active".
     */
    public MakeSignupRedeemable() {
        for(var transaction of this.transactions) {
            if(transaction.notes == "Signup" && transaction.expiryDate!.valueOf() >= Date.now()) {
                transaction.notes = "Signup points can be redeemed now."
            }
        }
        this.count()
    }

    /**
     *  This method will be called by the `Account` module when the condition for
     *  unlocking signup bonus is met.
     *  To move points from Referrals bucket `this.signup` to Redeemable bucket `this.redeemable`,
     *  we change the `notes` property of signup points issuance from "Signup" to 
     *  "Referral points can be redeemed now.".
     */
    public MakeReferralsRedeemable() {
        for(var transaction of this.transactions) {
            if(transaction.notes == "Referral" && transaction.expiryDate!.valueOf() >= Date.now()) {
                transaction.notes = "Referral points can now be redeemed."
            }
        }
        this.count()
    }
}

export { Transaction }
export { InitTransaction }
export { Points }