import * as Sequelize from 'sequelize'
import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto'
import { shareVideoByServerAndChannel } from '../../lib/activitypub/share'
import { getVideoActivityPubUrl, getVideoChannelActivityPubUrl } from '../../lib/activitypub/url'
import { createLocalAccountWithoutKeys } from '../../lib/user'
import { ApplicationModel } from '../../models/application/application'
import { SERVER_ACTOR_NAME } from '../constants'

async function up (utils: {
  transaction: Sequelize.Transaction,
  queryInterface: Sequelize.QueryInterface,
  sequelize: Sequelize.Sequelize,
  db: any
}): Promise<void> {
  const q = utils.queryInterface
  const db = utils.db

  // Assert there are no friends
  {
    const query = 'SELECT COUNT(*) as total FROM "Pods"'
    const options = {
      type: Sequelize.QueryTypes.SELECT
    }
    const res = await utils.sequelize.query(query, options)

    if (!res[0] || res[0].total !== 0) {
      throw new Error('You need to quit friends.')
    }
  }

  // Pods -> Servers
  await utils.queryInterface.renameTable('Pods', 'Servers')

  // Create Account table
  await db.Account.sync()

  // Create AccountFollows table
  await db.AccountFollow.sync()

  // Modify video abuse table
  await db.VideoAbuse.destroy({ truncate: true })
  await utils.queryInterface.removeColumn('VideoAbuses', 'reporterPodId')
  await utils.queryInterface.removeColumn('VideoAbuses', 'reporterUsername')

  // Create column link with Account table
  {
    const data = {
      type: Sequelize.INTEGER,
      allowNull: false,
      references: {
        model: 'Accounts',
        key: 'id'
      },
      onDelete: 'CASCADE'
    }
    await q.addColumn('VideoAbuses', 'reporterAccountId', data)
  }

  // Drop request tables
  await utils.queryInterface.dropTable('RequestToPods')
  await utils.queryInterface.dropTable('RequestVideoEvents')
  await utils.queryInterface.dropTable('RequestVideoQadus')
  await utils.queryInterface.dropTable('Requests')

  // Create application account
  {
    const applicationInstance = await ApplicationModel.findOne()
    const accountCreated = await createLocalAccountWithoutKeys(SERVER_ACTOR_NAME, null, applicationInstance.id, undefined)

    const { publicKey, privateKey } = await createPrivateAndPublicKeys()
    accountCreated.set('publicKey', publicKey)
    accountCreated.set('privateKey', privateKey)

    await accountCreated.save()
  }

  // Drop old video channel foreign key (referencing Authors)
  {
    const query = 'ALTER TABLE "VideoChannels" DROP CONSTRAINT "VideoChannels_authorId_fkey"'
    await utils.sequelize.query(query)
  }

  // Recreate accounts for each user
  const users = await db.User.findAll()
  for (const user of users) {
    const account = await createLocalAccountWithoutKeys(user.username, user.id, null, undefined)

    const { publicKey, privateKey } = await createPrivateAndPublicKeys()
    account.set('publicKey', publicKey)
    account.set('privateKey', privateKey)
    await account.save()
  }

  {
    const data = {
      type: Sequelize.INTEGER,
      allowNull: true,
      onDelete: 'CASCADE',
      reference: {
        model: 'Account',
        key: 'id'
      }
    }
    await q.addColumn('VideoChannels', 'accountId', data)

    {
      const query = 'UPDATE "VideoChannels" SET "accountId" = ' +
                    '(SELECT "Accounts"."id" FROM "Accounts" INNER JOIN "Authors" ON "Authors"."userId" = "Accounts"."userId" ' +
                    'WHERE "VideoChannels"."authorId" = "Authors"."id")'
      await utils.sequelize.query(query)
    }

    data.allowNull = false
    await q.changeColumn('VideoChannels', 'accountId', data)

    await q.removeColumn('VideoChannels', 'authorId')
  }

  // Add url column to "Videos"
  {
    const data = {
      type: Sequelize.STRING,
      defaultValue: null,
      allowNull: true
    }
    await q.addColumn('Videos', 'url', data)

    const videos = await db.Video.findAll()
    for (const video of videos) {
      video.url = getVideoActivityPubUrl(video)
      await video.save()
    }

    data.allowNull = false
    await q.changeColumn('Videos', 'url', data)
  }

  // Add url column to "VideoChannels"
  {
    const data = {
      type: Sequelize.STRING,
      defaultValue: null,
      allowNull: true
    }
    await q.addColumn('VideoChannels', 'url', data)

    const videoChannels = await db.VideoChannel.findAll()
    for (const videoChannel of videoChannels) {
      videoChannel.url = getVideoChannelActivityPubUrl(videoChannel)
      await videoChannel.save()
    }

    data.allowNull = false
    await q.changeColumn('VideoChannels', 'url', data)
  }

  // Loss old video rates, whatever
  await utils.queryInterface.dropTable('UserVideoRates')
  await db.AccountVideoRate.sync()

  {
    const data = {
      type: Sequelize.ENUM('transcoding', 'activitypub-http'),
      defaultValue: 'transcoding',
      allowNull: false
    }
    await q.addColumn('Jobs', 'category', data)
  }

  await db.VideoShare.sync()
  await db.VideoChannelShare.sync()

  {
    const videos = await db.Video.findAll({
      include: [
        {
          model: db.Video['sequelize'].models.VideoChannel,
          include: [
            {
              model: db.Video['sequelize'].models.Account,
              include: [ { model: db.Video['sequelize'].models.Server, required: false } ]
            }
          ]
        },
        {
          model: db.Video['sequelize'].models.AccountVideoRate,
          include: [ db.Video['sequelize'].models.Account ]
        },
        {
          model: db.Video['sequelize'].models.VideoShare,
          include: [ db.Video['sequelize'].models.Account ]
        },
        db.Video['sequelize'].models.Tag,
        db.Video['sequelize'].models.VideoFile
      ]
    })

    for (const video of videos) {
      await shareVideoByServerAndChannel(video, undefined)
    }
  }
}

function down (options) {
  throw new Error('Not implemented.')
}

export {
  up,
  down
}
