import SubscriptionGroupHandler from './SubscriptionGroupHandler.mjs'

import OError from '@overleaf/o-error'
import logger from '@overleaf/logger'
import Settings from '@overleaf/settings'
import SubscriptionLocator from './SubscriptionLocator.mjs'
import SessionManager from '../Authentication/SessionManager.mjs'
import UserAuditLogHandler from '../User/UserAuditLogHandler.mjs'
import { expressify } from '@overleaf/promise-utils'
import Modules from '../../infrastructure/Modules.mjs'
import UserGetter from '../User/UserGetter.mjs'
import { Subscription } from '../../models/Subscription.mjs'
import { z, parseReq } from '../../infrastructure/Validation.mjs'
import { isProfessionalGroupPlan } from './PlansHelper.mjs'
import {
  MissingBillingInfoError,
  ManuallyCollectedError,
  InactiveError,
  SubtotalLimitExceededError,
  HasPastDueInvoiceError,
  HasNoAdditionalLicenseWhenManuallyCollectedError,
  PaymentActionRequiredError,
  MultiplePendingChangesError,
} from './Errors.mjs'

const MAX_NUMBER_OF_USERS = 20
const MAX_NUMBER_OF_PO_NUMBER_CHARACTERS = 50

/**
 * @import { Subscription } from "../../../../types/subscription/dashboard/subscription.js"
 */

/**
 * @param {import("express").Request} req
 * @param {import("express").Response} res
 * @returns {Promise<void>}
 */
async function removeUserFromGroup(req, res) {
  const subscription = req.entity
  const userToRemoveId = req.params.user_id
  const loggedInUserId = SessionManager.getLoggedInUserId(req.session)
  const subscriptionId = subscription._id
  logger.debug(
    { subscriptionId, userToRemoveId },
    'removing user from group subscription'
  )

  await _removeUserFromGroup(req, res, {
    userToRemoveId,
    loggedInUserId,
    subscription,
  })
}

/**
 * @param {import("express").Request} req
 * @param {import("express").Response} res
 * @returns {Promise<void>}
 */
async function removeSelfFromGroup(req, res) {
  const userToRemoveId = SessionManager.getLoggedInUserId(req.session)
  const subscription = await SubscriptionLocator.promises.getSubscription(
    req.query.subscriptionId
  )

  await _removeUserFromGroup(req, res, {
    userToRemoveId,
    loggedInUserId: userToRemoveId,
    subscription,
  })
}

/**
 * @param {import("express").Request} req
 * @param {import("express").Response} res
 * @param {string} userToRemoveId
 * @param {string} loggedInUserId
 * @param {Subscription} subscription
 * @returns {Promise<void>}
 * @private
 */
async function _removeUserFromGroup(
  req,
  res,
  { userToRemoveId, loggedInUserId, subscription }
) {
  const subscriptionId = subscription._id

  const groupSSOActive = (
    await Modules.promises.hooks.fire('hasGroupSSOEnabled', subscription)
  )?.[0]
  if (groupSSOActive) {
    await Modules.promises.hooks.fire(
      'unlinkUserFromGroupSSO',
      userToRemoveId,
      subscriptionId
    )
  }

  try {
    await UserAuditLogHandler.promises.addEntry(
      userToRemoveId,
      'remove-from-group-subscription',
      loggedInUserId,
      req.ip,
      { subscriptionId }
    )
  } catch (auditLogError) {
    throw OError.tag(auditLogError, 'error adding audit log entry', {
      userToRemoveId,
      subscriptionId,
    })
  }

  const groupAuditLog = {
    initiatorId: loggedInUserId,
    ipAddress: req.ip,
  }

  try {
    await SubscriptionGroupHandler.promises.removeUserFromGroup(
      subscriptionId,
      userToRemoveId,
      groupAuditLog
    )
  } catch (error) {
    logger.err(
      { err: error, userToRemoveId, subscriptionId },
      'error removing self from group'
    )
    return res.sendStatus(500)
  }

  res.sendStatus(200)
}

/**
 * @param {import("express").Request} req
 * @param {import("express").Response} res
 * @returns {Promise<void>}
 */
async function addSeatsToGroupSubscription(req, res) {
  try {
    const userId = SessionManager.getLoggedInUserId(req.session)
    const { subscription, paymentProviderSubscription, plan } =
      await SubscriptionGroupHandler.promises.getUsersGroupSubscriptionDetails(
        userId
      )
    await SubscriptionGroupHandler.promises.ensureFlexibleLicensingEnabled(plan)
    await SubscriptionGroupHandler.promises.ensureSubscriptionIsActive(
      subscription
    )
    await SubscriptionGroupHandler.promises.ensureSubscriptionHasNoPastDueInvoice(
      subscription
    )
    await SubscriptionGroupHandler.promises.checkBillingInfoExistence(
      paymentProviderSubscription,
      userId
    )
    await SubscriptionGroupHandler.promises.ensureSubscriptionHasAdditionalLicenseAddOnWhenCollectionMethodIsManual(
      paymentProviderSubscription
    )

    res.render('subscriptions/add-seats', {
      subscriptionId: subscription._id,
      groupName: subscription.teamName,
      totalLicenses: subscription.membersLimit,
      isProfessional: isProfessionalGroupPlan(subscription),
      isCollectionMethodManual:
        paymentProviderSubscription.isCollectionMethodManual,
      redirectedPaymentErrorCode: req.query.errorCode,
    })
  } catch (error) {
    if (error instanceof MissingBillingInfoError) {
      return res.redirect(
        '/user/subscription/group/missing-billing-information'
      )
    }

    if (error instanceof HasNoAdditionalLicenseWhenManuallyCollectedError) {
      return res.redirect(
        '/user/subscription/group/manually-collected-subscription?error_type=no-additional-license'
      )
    }

    if (
      error instanceof InactiveError ||
      error instanceof HasPastDueInvoiceError
    ) {
      return res.redirect('/user/subscription')
    }

    logger.err(
      { error },
      'error while getting users group subscription details'
    )

    return res.redirect('/user/subscription')
  }
}

const previewAddSeatsSubscriptionChangeSchema = z.object({
  body: z.object({
    adding: z.number().int().min(1).max(MAX_NUMBER_OF_USERS),
    poNumber: z.string().max(MAX_NUMBER_OF_PO_NUMBER_CHARACTERS).optional(),
  }),
})

/**
 * @param {import("express").Request} req
 * @param {import("express").Response} res
 * @returns {Promise<void>}
 */
async function previewAddSeatsSubscriptionChange(req, res) {
  const { body } = parseReq(req, previewAddSeatsSubscriptionChangeSchema)
  try {
    const userId = SessionManager.getLoggedInUserId(req.session)
    const preview =
      await SubscriptionGroupHandler.promises.previewAddSeatsSubscriptionChange(
        userId,
        body.adding
      )

    res.json(preview)
  } catch (error) {
    if (
      error instanceof MissingBillingInfoError ||
      error instanceof InactiveError ||
      error instanceof HasPastDueInvoiceError ||
      error instanceof HasNoAdditionalLicenseWhenManuallyCollectedError
    ) {
      return res.status(422).end()
    }

    if (error instanceof SubtotalLimitExceededError) {
      return res.status(422).json({
        code: 'subtotal_limit_exceeded',
        adding: body.adding,
      })
    }

    logger.err(
      { error },
      'error trying to preview "add seats" subscription change'
    )

    return res.status(500).end()
  }
}

/**
 * @param {import("express").Request} req
 * @param {import("express").Response} res
 * @returns {Promise<void>}
 */
async function createAddSeatsSubscriptionChange(req, res) {
  try {
    const userId = SessionManager.getLoggedInUserId(req.session)
    const create =
      await SubscriptionGroupHandler.promises.createAddSeatsSubscriptionChange(
        userId,
        req.body.adding,
        req.body.poNumber
      )

    res.json(create)
  } catch (error) {
    if (
      error instanceof MissingBillingInfoError ||
      error instanceof MultiplePendingChangesError ||
      error instanceof InactiveError ||
      error instanceof HasPastDueInvoiceError ||
      error instanceof HasNoAdditionalLicenseWhenManuallyCollectedError
    ) {
      return res.status(422).end()
    }

    if (error instanceof SubtotalLimitExceededError) {
      return res.status(422).json({
        code: 'subtotal_limit_exceeded',
        adding: req.body.adding,
      })
    }

    if (error instanceof PaymentActionRequiredError) {
      return res.status(402).json({
        message: 'Payment action required',
        clientSecret: error.info.clientSecret,
        publicKey: error.info.publicKey,
      })
    }

    logger.err(
      { error },
      'error trying to create "add seats" subscription change'
    )

    return res.status(500).end()
  }
}

const submitFormSchema = z.object({
  body: z.object({
    adding: z.coerce.number().int().min(MAX_NUMBER_OF_USERS),
    poNumber: z.string().optional(),
  }),
})

async function submitForm(req, res) {
  const { body } = parseReq(req, submitFormSchema)
  const { adding, poNumber } = body

  const userId = SessionManager.getLoggedInUserId(req.session)
  const userEmail = await UserGetter.promises.getUserEmail(userId)

  const { paymentProviderSubscription, subscription } =
    await SubscriptionGroupHandler.promises.getUsersGroupSubscriptionDetails(
      userId
    )

  if (paymentProviderSubscription.isCollectionMethodManual) {
    await SubscriptionGroupHandler.promises.updateSubscriptionPaymentTerms(
      paymentProviderSubscription,
      poNumber
    )
  }

  const messageLines = [`\n**Overleaf Sales Contact Form:**`]
  messageLines.push('**Subject:** Self-Serve Group User Increase Request')
  messageLines.push(`**Estimated Number of Users:** ${adding}`)
  messageLines.push(
    `**Subscription:** [${subscription._id}](${Settings.adminUrl}/admin/subscription/${subscription._id})`
  )
  messageLines.push(`**Current Number of Seats:** ${subscription.membersLimit}`)
  messageLines.push(`**Plan Code:** ${subscription.planCode}`)
  if (poNumber) {
    messageLines.push(`**PO Number:** ${poNumber}`)
  }
  if (subscription.salesforce_id) {
    messageLines.push(
      `**Salesforce ID:** [${subscription.salesforce_id}](https://digitalscience.lightning.force.com/lightning/r/Opportunity/${subscription.salesforce_id}/view)`
    )
  }
  messageLines.push(
    `**Message:** This email has been generated on behalf of user with email **${userEmail}** ` +
      'to request an increase in the total number of users for their subscription.'
  )
  const messageFormatted = messageLines.join('\n\n')

  const data = {
    email: userEmail,
    subject: 'Sales Contact Form',
    message: messageFormatted,
    inbox: 'sales',
  }

  await Modules.promises.hooks.fire('sendSupportRequest', data)
  res.sendStatus(204)
}

async function subscriptionUpgradePage(req, res) {
  try {
    const userId = SessionManager.getLoggedInUserId(req.session)
    const changePreview =
      await SubscriptionGroupHandler.promises.getGroupPlanUpgradePreview(userId)
    const olSubscription = await Subscription.findOne({
      admin_id: userId,
    }).exec()
    res.render('subscriptions/upgrade-group-subscription-react', {
      changePreview,
      totalLicenses: olSubscription.membersLimit,
      groupName: olSubscription.teamName,
      redirectedPaymentErrorCode: req.query.errorCode,
    })
  } catch (error) {
    if (error instanceof MissingBillingInfoError) {
      return res.redirect(
        '/user/subscription/group/missing-billing-information'
      )
    }

    if (error instanceof ManuallyCollectedError) {
      return res.redirect(
        '/user/subscription/group/manually-collected-subscription?error_type=plan-upgrade'
      )
    }

    if (error instanceof SubtotalLimitExceededError) {
      return res.redirect('/user/subscription/group/subtotal-limit-exceeded')
    }

    if (error instanceof InactiveError) {
      return res.redirect('/user/subscription')
    }

    logger.err({ error }, 'error loading upgrade subscription page')

    return res.redirect('/user/subscription')
  }
}

async function upgradeSubscription(req, res) {
  try {
    const userId = SessionManager.getLoggedInUserId(req.session)
    await SubscriptionGroupHandler.promises.upgradeGroupPlan(userId)
    return res.sendStatus(200)
  } catch (error) {
    if (error instanceof PaymentActionRequiredError) {
      return res.status(402).json({
        message: 'Payment action required',
        clientSecret: error.info.clientSecret,
        publicKey: error.info.publicKey,
      })
    }
    if (error instanceof MultiplePendingChangesError) {
      return res.status(422).json({
        code: 'multiple_pending_changes',
        message:
          'Cannot upgrade subscription while there are multiple pending subscription changes. Please contact support.',
      })
    }
    logger.err({ error }, 'error trying to upgrade subscription')
    return res.sendStatus(500)
  }
}

async function missingBillingInformation(req, res) {
  try {
    const userId = SessionManager.getLoggedInUserId(req.session)
    const subscription =
      await SubscriptionLocator.promises.getUsersSubscription(userId)

    res.render('subscriptions/missing-billing-information', {
      groupName: subscription.teamName,
    })
  } catch (error) {
    logger.err(
      { error },
      'error trying to render missing billing information page'
    )
    return res.redirect('/user/subscription')
  }
}

async function manuallyCollectedSubscription(req, res) {
  try {
    const userId = SessionManager.getLoggedInUserId(req.session)
    const subscription =
      await SubscriptionLocator.promises.getUsersSubscription(userId)

    res.render('subscriptions/manually-collected-subscription', {
      groupName: subscription.teamName,
      errorType: req.query.error_type,
    })
  } catch (error) {
    logger.err(
      { error },
      'error trying to render manually collected subscription page'
    )
    return res.redirect('/user/subscription')
  }
}

async function subtotalLimitExceeded(req, res) {
  try {
    const userId = SessionManager.getLoggedInUserId(req.session)
    const subscription =
      await SubscriptionLocator.promises.getUsersSubscription(userId)

    res.render('subscriptions/subtotal-limit-exceeded', {
      groupName: subscription.teamName,
    })
  } catch (error) {
    logger.err({ error }, 'error trying to render subtotal limit exceeded page')
    return res.redirect('/user/subscription')
  }
}

async function getGroupPlanPerUserPrices(req, res) {
  try {
    const userId = SessionManager.getLoggedInUserId(req.session)
    const prices = await Modules.promises.hooks.fire(
      'getGroupPlanPerUserPrices',
      userId,
      req.query.currency
    )
    return res.json(prices[0])
  } catch (error) {
    logger.err({ error }, 'error trying to get websale group product prices')
    return res.sendStatus(500)
  }
}

export default {
  removeUserFromGroup: expressify(removeUserFromGroup),
  removeSelfFromGroup: expressify(removeSelfFromGroup),
  addSeatsToGroupSubscription: expressify(addSeatsToGroupSubscription),
  submitForm: expressify(submitForm),
  previewAddSeatsSubscriptionChange: expressify(
    previewAddSeatsSubscriptionChange
  ),
  createAddSeatsSubscriptionChange: expressify(
    createAddSeatsSubscriptionChange
  ),
  subscriptionUpgradePage: expressify(subscriptionUpgradePage),
  upgradeSubscription: expressify(upgradeSubscription),
  missingBillingInformation: expressify(missingBillingInformation),
  manuallyCollectedSubscription: expressify(manuallyCollectedSubscription),
  subtotalLimitExceeded: expressify(subtotalLimitExceeded),
  getGroupPlanPerUserPrices: expressify(getGroupPlanPerUserPrices),
}
