/**
 * Copyright (c) 2021 Gitpod GmbH. All rights reserved.
 * Licensed under the GNU Affero General Public License (AGPL).
 * See License-AGPL.txt in the project root for license information.
 */

 import * as uuidv4 from 'uuid/v4';
import { Without } from "./util/without";
import { User } from './protocol';
import { oneMonthLater } from './util/timeutil';

/*
 * Subscription and acocunting data
 */
export interface AccountEntry {
    uid: string;

    userId: string;

    /** [hours] */
    amount: number;

    /**
     * credit: start of validity,
     * session: end of (split-) session
     */
    date: string;

    /**
     * debits (session, expiry, loss): relation to credit
     */
    creditId?: string;

    /**
     * credit: end of validity
     */
    expiryDate?: string;         // exclusive

    kind: AccountEntryKind;

    /**
     * credit: amount - accounted debits
     * [hours]
     */
    remainingAmount?: number;

    description?: object;
}
export namespace AccountEntry {
    export function create<T extends AccountEntry>(entry: Without<T, 'uid'>): T {
        const result = entry as T;
        result.uid = uuidv4();
        return result;
    };
}

export type DebitAccountEntryKind = 'session' | 'expiry' | 'loss';
export type AccountEntryKind = 'credit' | DebitAccountEntryKind | 'carry' | 'open';

export interface Credit extends AccountEntry {
    kind: 'credit';
    expiryDate: string;
}
export type Debit = LossDebit | ExpiryDebit | SessionDebit;
export interface LossDebit extends AccountEntry {
    kind: 'loss';
}
export interface ExpiryDebit extends AccountEntry {
    kind: 'expiry';
    creditId: undefined;
}
export interface SessionDebit extends AccountEntry {
    kind: DebitAccountEntryKind;
    creditId: string;
}

export type AccountEntryDescription = SessionDescription | CreditDescription;
export interface CreditDescription {
    subscriptionId: string;
    planId: string;
}
export namespace CreditDescription {
    export function is(obj: any): obj is CreditDescription {
        return !!obj
            && obj.hasOwnProperty('subscriptionId')
            && obj.hasOwnProperty('planId');
    }
}
export interface SessionDescription {
    contextTitle: string;
    contextUrl: string;
    workspaceId: string;
    workspaceInstanceId: string;
    private: boolean;
}
export namespace SessionDescription {
    export function is(obj: any): obj is SessionDescription {
        return !!obj
            && obj.hasOwnProperty('contextTitle')
            && obj.hasOwnProperty('contextUrl')
            && obj.hasOwnProperty('workspaceId')
            && obj.hasOwnProperty('workspaceInstanceId')
            && obj.hasOwnProperty('private');
    }
}

/**
 * - The earliest subscription may start with User.creationDate
 * - There may be multiple Gitpod subscriptions for a user at any given time
 * - The dates form an interval of the form: [startDate, endDate)
 * - Subscriptions that directly map to a Chargebee plan have their paymentReference set and MAY carry additional paymentData (UserPaidSubscription)
 * - Subscriptions that are assigned to a user through a Team Subscription carry a teamSubscriptionSlotId (AssignedTeamSubscription)
 */
export interface Subscription {
    uid: string;
    userId: string;
    startDate: string;           // inclusive
    /** When the subscription will end (must be >= cancellationDate!) */
    endDate?: string;            // exclusive
    /** When the subscription was cancelled */
    cancellationDate?: string;   // exclusive
    /** Number of granted hours */
    amount: number;
    /** Number of granted hours for the first month: If this is set, use this value for the first month */
    firstMonthAmount?: number;
    planId?: string;
    paymentReference?: string;
    paymentData?: PaymentData;
    teamSubscriptionSlotId?: string;
    /** marks the subscription as deleted */
    deleted?: boolean;
}

export interface SubscriptionAndUser extends Subscription {
    user: User;
}

export interface PaymentData {
    /** Marks the date as of which the _switch_ is effective. */
    downgradeDate?: string;
    /** Determines the new plan the dowgrade is targeted against (optional for backwards compatibility) */
    newPlan?: string;
}

export interface UserPaidSubscription extends Subscription {
    paymentReference: string;
    paymentData?: PaymentData;
}
export namespace UserPaidSubscription {
    export function is(data: any): data is UserPaidSubscription {
        return !!data
            && data.hasOwnProperty('paymentReference');
    }
}

export interface AssignedTeamSubscription extends Subscription {
    teamSubscriptionSlotId: string;
}
export namespace AssignedTeamSubscription {
    export function is(data: any): data is AssignedTeamSubscription {
        return !!data
            && data.hasOwnProperty('teamSubscriptionSlotId');
    }
}

export namespace Subscription {
    export function create(newSubscription: Without<Subscription, 'uid'>) {
        const subscription = newSubscription as Subscription;
        subscription.uid = uuidv4();
        return subscription;
    };
    export function cancelSubscription(s: Subscription, cancellationDate: string, endDate?: string) {
        s.endDate = endDate || cancellationDate;
        s.cancellationDate = cancellationDate;
    };
    export function isSame(s1: Subscription | undefined, s2: Subscription | undefined): boolean {
        return !!s1 && !!s2
            && s1.userId === s2.userId
            && s1.planId === s2.planId
            && s1.startDate === s2.startDate
            && s1.endDate === s2.endDate
            && s1.amount === s2.amount
            && s1.cancellationDate === s2.cancellationDate
            && s1.deleted === s2.deleted
            && ((s1.paymentData === undefined && s2.paymentData === undefined)
                || (!!s1.paymentData && !!s2.paymentData
                    && s1.paymentData.downgradeDate === s2.paymentData.downgradeDate
                    && s1.paymentData.newPlan === s2.paymentData.newPlan));
    };
    export function isActive(s: Subscription, date: string): boolean {
        return s.startDate <= date && (s.endDate === undefined || date < s.endDate);
    };
    export function isDowngraded(s: Subscription) {
        return s.paymentData && s.paymentData.downgradeDate;
    };
    export function isCancelled(s: Subscription) {
        return !!s.cancellationDate;
    };
    export function calculateCurrentPeriod(startDate: string, now: Date) {
        let nextStartDate = startDate;
        do {
            startDate = nextStartDate;
            nextStartDate = oneMonthLater(startDate, new Date(startDate).getDate());
        } while (nextStartDate < now.toISOString());
        return { startDate, endDate: nextStartDate };
    };
}

export type MaybeSubscription = Subscription | undefined;

export interface Period {
    startDate: string;           // inclusive
    endDate: string;             // exclusive
}

export type MaybePeriod = Period | undefined;

export type AccountEntryFixedPeriod = Without<AccountEntry, 'uid'> & { expiryDate: string };
export interface AccountStatement extends Period {
    userId: string;
    /**
     * The subscriptions that have not been cancelled yet at the end of the period
     */
    subscriptions: Subscription[];
    credits: Credit[];
    debits: Debit[];
    /** Remaining valid hours (accumulated from credits) */
    remainingHours: RemainingHours;
}
export type RemainingHours = number | 'unlimited';

export interface CreditAlert {
    userId: string,
    remainingUsageHours: number
}
