import {models} from '../models'
import {success, failure} from '../utils/res'
import {CronJob} from 'cron'
import {toCamel} from '../utils/case'
import * as cronUtils from '../utils/cron'
import * as socket from '../utils/socket'
import * as jsonUtils from '../utils/json'
import * as helpers from '../helpers'
import * as rsa from '../crypto/rsa'
import * as moment from 'moment'
import * as path from 'path'
import * as network from '../network'

const constants = require(path.join(__dirname,'../../config/constants.json'))

// store all current running jobs in memory
let jobs = {}

// init jobs from DB
const initializeCronJobs = async () => {
  await helpers.sleep(1000)
  const subs = await getRawSubs({ where: { ended: false } })
  subs.length && subs.forEach(sub => {
    console.log("=> starting subscription cron job",sub.id+":",sub.cron)
    startCronJob(sub)
  })
}

async function startCronJob(sub) {
  jobs[sub.id] = new CronJob(sub.cron, async function () {
    const subscription = await models.Subscription.findOne({ where: { id: sub.id } })
    if (!subscription) {
      delete jobs[sub.id]
      return this.stop()
    }

    console.log('EXEC CRON =>', subscription.id)
    if (subscription.paused) { // skip, still in jobs{} tho
      return this.stop()
    }
    let STOP = checkSubscriptionShouldAlreadyHaveEnded(subscription)
    if (STOP) { // end the job and return
      console.log("stop")
      subscription.update({ ended: true })
      delete jobs[subscription.id]
      return this.stop()
    }
    // SEND PAYMENT!!!
    sendSubscriptionPayment(subscription, false)
  }, null, true);
}

function checkSubscriptionShouldAlreadyHaveEnded(sub) {
  if (sub.endDate) {
    const now = new Date()
    if (now.getTime() > sub.endDate.getTime()) {
      return true
    }
  }
  if (sub.endNumber) {
    if (sub.count >= sub.endNumber) {
      return true
    }
  }
  return false
}

function checkSubscriptionShouldEndAfterThisPayment(sub) {
  if (sub.endDate) {
    const { ms } = cronUtils.parse(sub.cron)
    const now = new Date()
    if ((now.getTime() + ms) > sub.endDate.getTime()) {
      return true
    }
  }
  if (sub.endNumber) {
    if (sub.count + 1 >= sub.endNumber) {
      return true
    }
  }
  return false
}

function msgForSubPayment(owner, sub, isFirstMessage, forMe){
  let text = ''
  if (isFirstMessage) {
    const alias = forMe ? 'You' : owner.alias
    text = `${alias} subscribed\n`
  } else {
    text = 'Subscription\n'
  }
  text += `Amount: ${sub.amount} sats\n`
  text += `Interval: ${cronUtils.parse(sub.cron).interval}\n`
  if(sub.endDate) {
    text += `End: ${moment(sub.endDate).format('MM/DD/YY')}\n`
    text += `Status: ${sub.count+1} sent`
  } else if(sub.endNumber) {
    text += `Status: ${sub.count+1} of ${sub.endNumber} sent`
  }
  return text
}

async function sendSubscriptionPayment(sub, isFirstMessage) {
  const owner = await models.Contact.findOne({ where: { isOwner: true } })

  var date = new Date();
  date.setMilliseconds(0)

  const subscription = await models.Subscription.findOne({ where: { id: sub.id } })
  if (!subscription) {
    return
  }
  const chat = await models.Chat.findOne({ where: {id:subscription.chatId} })
  if (!subscription) {
    console.log("=> no sub for this payment!!!")
    return
  }

  const forMe = false
  const text = msgForSubPayment(owner, sub, isFirstMessage, forMe)

  const contact = await models.Contact.findByPk(sub.contactId)
  const enc = rsa.encrypt(contact.contactKey, text)

  network.sendMessage({
    chat: chat,
    sender: owner,
    type: constants.message_types.direct_payment,
    message: { amount: sub.amount, content: enc },
    amount: sub.amount,
    success: async (data) => {
      const shouldEnd = checkSubscriptionShouldEndAfterThisPayment(subscription)
      const obj = {
        totalPaid: parseFloat(subscription.totalPaid||0) + parseFloat(subscription.amount),
        count: parseInt(subscription.count||0) + 1,
        ended: false,
      }
      if(shouldEnd) {
        obj.ended = true
        if(jobs[sub.id]) jobs[subscription.id].stop()
        delete jobs[subscription.id]
      }
      await subscription.update(obj)

      const forMe = true
      const text2 = msgForSubPayment(owner, sub, isFirstMessage, forMe)
      const encText = rsa.encrypt(owner.contactKey, text2)
      const message = await models.Message.create({
        chatId: chat.id,
        sender: owner.id,
        type: constants.message_types.direct_payment,
        status: constants.statuses.confirmed,
        messageContent: encText,
        amount: subscription.amount,
        amountMsat: parseFloat(subscription.amount) * 1000,
        date: date,
        createdAt: date,
        updatedAt: date,
        subscriptionId: subscription.id,
      })
      socket.sendJson({
        type: 'direct_payment',
        response: jsonUtils.messageToJson(message, chat)
      })
    },
    failure: async (err) => {
      console.log("SEND PAY ERROR")
      let errMessage = constants.payment_errors[err] || 'Unknown'
      errMessage = 'Payment Failed: ' + errMessage
      const message = await models.Message.create({
        chatId: chat.id,
        sender: owner.id,
        type: constants.message_types.direct_payment,
        status: constants.statuses.failed,
        messageContent: errMessage,
        amount: sub.amount,
        amountMsat: parseFloat(sub.amount) * 1000,
        date: date,
        createdAt: date,
        updatedAt: date,
        subscriptionId: sub.id,
      })
      socket.sendJson({
        type: 'direct_payment',
        response: jsonUtils.messageToJson(message, chat)
      })
    }
  })
}

// pause sub
async function pauseSubscription(req, res) {
  const id = parseInt(req.params.id)
  try {
    const sub = await models.Subscription.findOne({ where: { id } })
    if (sub) {
      sub.update({ paused: true })
      if (jobs[id]) jobs[id].stop()
      success(res, jsonUtils.subscriptionToJson(sub,null))
    } else {
      failure(res, 'not found')
    }
  } catch (e) {
    console.log('ERROR pauseSubscription', e)
    failure(res, e)
  }
};

// restart sub
async function restartSubscription(req, res) {
  const id = parseInt(req.params.id)
  try {
    const sub = await models.Subscription.findOne({ where: { id } })
    if (sub) {
      sub.update({ paused: false })
      if (jobs[id]) jobs[id].start()
      success(res, jsonUtils.subscriptionToJson(sub,null))
    } else {
      failure(res, 'not found')
    }
  } catch (e) {
    console.log('ERROR restartSubscription', e)
    failure(res, e)
  }
};

async function getRawSubs(opts = {}) {
  const options: {[k: string]: any} = { order: [['id', 'asc']], ...opts }
  try {
    const subs = await models.Subscription.findAll(options)
    return subs
  } catch (e) {
    throw e
  }
}

// all subs
const getAllSubscriptions = async (req, res) => {
  try {
    const subs = await getRawSubs()
    success(res, subs.map(sub => jsonUtils.subscriptionToJson(sub,null)))
  } catch (e) {
    console.log('ERROR getAllSubscriptions', e)
    failure(res, e)
  }
};

// one sub by id
async function getSubscription(req, res) {
  try {
    const sub = await models.Subscription.findOne({ where: { id: req.params.id } })
    success(res, jsonUtils.subscriptionToJson(sub,null))
  } catch (e) {
    console.log('ERROR getSubscription', e)
    failure(res, e)
  }
};

// delete sub by id
async function deleteSubscription(req, res) {
  const id = req.params.id
  if (!id) return
  try {
    if (jobs[id]) {
      jobs[id].stop()
      delete jobs[id]
    }
    models.Subscription.destroy({ where: { id } })
    success(res, true)
  } catch (e) {
    console.log('ERROR deleteSubscription', e)
    failure(res, e)
  }
};

// all subs for contact id
const getSubscriptionsForContact = async (req, res) => {
  try {
    const subs = await getRawSubs({ where: { contactId: req.params.contactId } })
    success(res, subs.map(sub => jsonUtils.subscriptionToJson(sub,null)))
  } catch (e) {
    console.log('ERROR getSubscriptionsForContact', e)
    failure(res, e)
  }
};

// create new sub
async function createSubscription(req, res) {
  const date = new Date()
  date.setMilliseconds(0)
  const s = jsonToSubscription({
    ...req.body,
    count: 0,
    total_paid: 0,
    createdAt: date,
    ended: false,
    paused: false
  })
  if(!s.cron){
    return failure(res, 'Invalid interval')
  }
  try {
    const owner = await models.Contact.findOne({ where: { isOwner: true } })
    const chat = await helpers.findOrCreateChat({
      chat_id: req.body.chat_id,
      owner_id: owner.id,
      recipient_id: req.body.contact_id,
    })
    s.chatId = chat.id // add chat id if newly created
    if(!owner || !chat){
      return failure(res, 'Invalid chat or contact')
    }
    const sub = await models.Subscription.create(s)
    startCronJob(sub)
    const isFirstMessage = true
    sendSubscriptionPayment(sub, isFirstMessage)
    success(res, jsonUtils.subscriptionToJson(sub, chat))
  } catch (e) {
    console.log('ERROR createSubscription', e)
    failure(res, e)
  }
};

async function editSubscription(req, res) {
  console.log('======> editSubscription')
  const date = new Date()
  date.setMilliseconds(0)
  const id = parseInt(req.params.id)
  const s = jsonToSubscription({
    ...req.body,
    count: 0,
    createdAt: date,
    ended: false,
    paused: false
  })
  try {
    if(!id || !s.chatId || !s.cron){
      return failure(res, 'Invalid data')
    }
    const subRecord = await models.Subscription.findOne({ where: { id }})
    if(!subRecord) {
      return failure(res, 'No subscription found')
    }
    // stop so it can be restarted
    if (jobs[id]) jobs[id].stop()
    const obj: {[k: string]: any} = {
      cron: s.cron,
      updatedAt: date,
    }
    if(s.amount) obj.amount = s.amount
    if(s.endDate) obj.endDate = s.endDate
    if(s.endNumber) obj.endNumber = s.endNumber

    const sub = await subRecord.update(obj)
    const end = checkSubscriptionShouldAlreadyHaveEnded(sub)
    if(end) {
      await subRecord.update({ended:true})
      delete jobs[id]
    } else {
      startCronJob(sub) // restart
    }
    const chat = await models.Chat.findOne({ where: { id: s.chatId }})
    success(res, jsonUtils.subscriptionToJson(sub, chat))
  } catch (e) {
    console.log('ERROR createSubscription', e)
    failure(res, e)
  }
};

function jsonToSubscription(j) {
  console.log("=>",j)
  const cron = cronUtils.make(j.interval)
  return toCamel({
    ...j,
    cron,
  })
}

export {
  initializeCronJobs,
  getAllSubscriptions,
  getSubscription,
  createSubscription,
  getSubscriptionsForContact,
  pauseSubscription,
  restartSubscription,
  deleteSubscription,
  editSubscription,
}