import { apiError } from '../../../../../../website/server/libs/apiError';
import common from '../../../../../../website/common';
import {
  getOneTimePaymentInfo,
  applyGemPayment,
} from '../../../../../../website/server/libs/payments/stripe/oneTimePayments';
import * as subscriptions from '../../../../../../website/server/libs/payments/stripe/subscriptions';
import { model as User } from '../../../../../../website/server/models/user';
import payments from '../../../../../../website/server/libs/payments/payments';

const { i18n } = common;

describe('Stripe - One Time Payments', () => {
  describe('getOneTimePaymentInfo', () => {
    let user;

    beforeEach(() => {
      user = new User();
      sandbox.stub(subscriptions, 'checkSubData');
    });

    describe('gemsBlock', () => {
      it('returns the gemsBlock and amount', async () => {
        const { gemsBlock, amount, subscription } = await getOneTimePaymentInfo('21gems', null, user);
        expect(gemsBlock).to.equal(common.content.gems['21gems']);
        expect(amount).to.equal(gemsBlock.price);
        expect(amount).to.equal(499);
        expect(subscription).to.be.null;
        expect(subscriptions.checkSubData).to.not.be.called;
      });

      it('throws if the gemsBlock does not exist', async () => {
        await expect(getOneTimePaymentInfo('not existant', null, user))
          .to.eventually.be.rejected.and.to.eql({
            httpCode: 400,
            name: 'BadRequest',
            message: apiError('invalidGemsBlock'),
          });
      });

      it('throws if the user cannot receive gems', async () => {
        sandbox.stub(user, 'canGetGems').resolves(false);
        await expect(getOneTimePaymentInfo('21gems', null, user))
          .to.eventually.be.rejected.and.to.eql({
            httpCode: 401,
            name: 'NotAuthorized',
            message: i18n.t('groupPolicyCannotGetGems'),
          });
      });
    });

    describe('gift', () => {
      it('throws if the receiver does not exist', async () => {
        const gift = {
          type: 'gems',
          uuid: 'invalid',
          gems: {
            amount: 3,
          },
        };

        await expect(getOneTimePaymentInfo(null, gift, user))
          .to.eventually.be.rejected.and.to.eql({
            httpCode: 404,
            name: 'NotFound',
            message: i18n.t('userWithIDNotFound', { userId: 'invalid' }),
          });
      });

      it('throws if the user cannot receive gems', async () => {
        const receivingUser = new User();
        await receivingUser.save();
        sandbox.stub(User.prototype, 'canGetGems').resolves(false);

        const gift = {
          type: 'gems',
          uuid: receivingUser._id,
          gems: {
            amount: 2,
          },
        };

        await expect(getOneTimePaymentInfo(null, gift, user))
          .to.eventually.be.rejected.and.to.eql({
            httpCode: 401,
            name: 'NotAuthorized',
            message: i18n.t('groupPolicyCannotGetGems'),
          });
      });

      it('throws if the amount of gems is <= 0', async () => {
        const receivingUser = new User();
        await receivingUser.save();
        const gift = {
          type: 'gems',
          uuid: receivingUser._id,
          gems: {
            amount: 0,
          },
        };

        await expect(getOneTimePaymentInfo(null, gift, user))
          .to.eventually.be.rejected.and.to.eql({
            httpCode: 400,
            name: 'BadRequest',
            message: i18n.t('badAmountOfGemsToPurchase'),
          });
      });

      it('throws if the subscription block does not exist', async () => {
        const receivingUser = new User();
        await receivingUser.save();
        const gift = {
          type: 'subscription',
          uuid: receivingUser._id,
          subscription: {
            key: 'invalid',
          },
        };

        await expect(getOneTimePaymentInfo(null, gift, user))
          .to.eventually.throw;
      });

      it('returns the amount (gems)', async () => {
        const receivingUser = new User();
        await receivingUser.save();
        const gift = {
          type: 'gems',
          uuid: receivingUser._id,
          gems: {
            amount: 4,
          },
        };

        expect(subscriptions.checkSubData).to.not.be.called;

        const { gemsBlock, amount, subscription } = await getOneTimePaymentInfo(null, gift, user);
        expect(gemsBlock).to.equal(null);
        expect(amount).to.equal('100');
        expect(subscription).to.be.null;
      });

      it('returns the amount (subscription)', async () => {
        const receivingUser = new User();
        await receivingUser.save();
        const gift = {
          type: 'subscription',
          uuid: receivingUser._id,
          subscription: {
            key: 'basic_3mo',
          },
        };
        const sub = common.content.subscriptionBlocks['basic_3mo']; // eslint-disable-line dot-notation

        const { gemsBlock, amount, subscription } = await getOneTimePaymentInfo(null, gift, user);

        expect(subscriptions.checkSubData).to.be.calledOnce;
        expect(subscriptions.checkSubData).to.be.calledWith(sub, false, null);

        expect(gemsBlock).to.equal(null);
        expect(amount).to.equal('1500');
        expect(Number(amount)).to.equal(sub.price * 100);
        expect(subscription).to.equal(sub);
      });
    });
  });

  describe('applyGemPayment', () => {
    let user;
    let customerId;
    let subKey;
    let userFindByIdStub;
    let paymentsCreateSubSpy;
    let paymentBuyGemsStub;

    beforeEach(async () => {
      subKey = 'basic_3mo';

      user = new User();
      await user.save();

      customerId = 'test-id';

      paymentsCreateSubSpy = sandbox.stub(payments, 'createSubscription');
      paymentsCreateSubSpy.resolves({});

      paymentBuyGemsStub = sandbox.stub(payments, 'buyGems');
      paymentBuyGemsStub.resolves({});
    });

    it('throws if the user does not exist', async () => {
      const metadata = { userId: 'invalid' };
      const session = { metadata, customer: customerId };

      await expect(applyGemPayment(session))
        .to.eventually.be.rejected.and.to.eql({
          httpCode: 404,
          name: 'NotFound',
          message: i18n.t('userWithIDNotFound', { userId: metadata.userId }),
        });
    });

    it('throws if the receiving user does not exist', async () => {
      const metadata = { userId: 'invalid' };
      const session = { metadata, customer: customerId };

      await expect(applyGemPayment(session))
        .to.eventually.be.rejected.and.to.eql({
          httpCode: 404,
          name: 'NotFound',
          message: i18n.t('userWithIDNotFound', { userId: metadata.userId }),
        });
    });

    it('throws if the gems block does not exist', async () => {
      const gift = {
        type: 'gems',
        uuid: 'invalid',
        gems: {
          amount: 16,
        },
      };

      const metadata = { userId: user._id, gift: JSON.stringify(gift) };
      const session = { metadata, customer: customerId };

      await expect(applyGemPayment(session))
        .to.eventually.be.rejected.and.to.eql({
          httpCode: 404,
          name: 'NotFound',
          message: i18n.t('userWithIDNotFound', { userId: 'invalid' }),
        });
    });

    describe('with existing user', () => {
      beforeEach(() => {
        const execStub = sandbox.stub().resolves(user);
        userFindByIdStub = sandbox.stub(User, 'findById');
        userFindByIdStub.withArgs(user._id).returns({ exec: execStub });
      });

      it('buys gems', async () => {
        const metadata = { userId: user._id, gemsBlock: '21gems' };
        const session = { metadata, customer: customerId };

        await applyGemPayment(session);

        expect(paymentBuyGemsStub).to.be.calledOnce;
        expect(paymentBuyGemsStub).to.be.calledWith({
          user,
          customerId,
          paymentMethod: 'Stripe',
          gift: undefined,
          gemsBlock: common.content.gems['21gems'],
        });
      });

      it('gift gems', async () => {
        const receivingUser = new User();
        const execStub = sandbox.stub().resolves(receivingUser);
        userFindByIdStub.withArgs(receivingUser._id).returns({ exec: execStub });
        const gift = {
          type: 'gems',
          uuid: receivingUser._id,
          gems: {
            amount: 16,
          },
        };

        sandbox.stub(JSON, 'parse').returns(gift);
        const metadata = { userId: user._id, gift: JSON.stringify(gift) };
        const session = { metadata, customer: customerId };

        await applyGemPayment(session);

        expect(paymentBuyGemsStub).to.be.calledOnce;
        expect(paymentBuyGemsStub).to.be.calledWith({
          user,
          customerId,
          paymentMethod: 'Gift',
          gift,
          gemsBlock: undefined,
        });
      });

      it('gift sub', async () => {
        const receivingUser = new User();
        const execStub = sandbox.stub().resolves(receivingUser);
        userFindByIdStub.withArgs(receivingUser._id).returns({ exec: execStub });
        const gift = {
          type: 'subscription',
          uuid: receivingUser._id,
          subscription: {
            key: subKey,
          },
        };

        sandbox.stub(JSON, 'parse').returns(gift);
        const metadata = { userId: user._id, gift: JSON.stringify(gift) };
        const session = { metadata, customer: customerId };

        await applyGemPayment(session);

        expect(paymentsCreateSubSpy).to.be.calledOnce;
        expect(paymentsCreateSubSpy).to.be.calledWith({
          user,
          customerId,
          paymentMethod: 'Gift',
          gift,
          autoRenews: false,
          gemsBlock: undefined,
        });
      });
    });
  });
});
