import _ from 'lodash'
import Path from 'node:path'
import { fileURLToPath } from 'node:url'
import { expressify } from '@overleaf/promise-utils'
import logger from '@overleaf/logger'
import Metrics from '@overleaf/metrics'
import SessionManager from '../../../../app/src/Features/Authentication/SessionManager.mjs'
import PrivilegeLevels from '../../../../app/src/Features/Authorization/PrivilegeLevels.mjs'
import ProjectHelper from '../../../../app/src/Features/Project/ProjectHelper.mjs'
import ProjectGetter from '../../../../app/src/Features/Project/ProjectGetter.mjs'
import ProjectDeleter from '../../../../app/src/Features/Project/ProjectDeleter.mjs'
import UserSettingsHelper from '../../../../app/src/Features/Project/UserSettingsHelper.mjs'
import UserGetter from '../../../../app/src/Features/User/UserGetter.mjs'
import { User } from '../../../../app/src/models/User.mjs'
import { Project } from '../../../../app/src/models/Project.mjs'
import { DeletedProject } from '../../../../app/src/models/DeletedProject.mjs'
import { OError } from '../../../../app/src/Features/Errors/Errors.js'
import HttpErrorHandler from '../../../../app/src/Features/Errors/HttpErrorHandler.mjs'
import SplitTestHandler from '../../../../app/src/Features/SplitTests/SplitTestHandler.mjs'

const __dirname = Path.dirname(fileURLToPath(import.meta.url))

function cleanupSession(req) {
  // cleanup redirects at the end of the redirect chain
  delete req.session.postCheckoutRedirect
  delete req.session.postLoginRedirect
  delete req.session.postOnboardingRedirect
}

async function manageProjectsPage(req, res, next) {
  cleanupSession(req)

  const projectsBlobPending = _getProjects().catch(err => {
    logger.err({ err }, 'projects listing in background failed')
    return undefined
  })

  const prefetchedProjectsBlob = await projectsBlobPending

  Metrics.inc('project-list-prefetch-projects', 1, {
    status: prefetchedProjectsBlob ? 'success' : 'error',
  })

  const userId = SessionManager.getLoggedInUserId(req.session)
  const user = await User.findById(userId, 'ace')

  const userSettings = await UserSettingsHelper.buildUserSettings(
    req,
    res,
    user
  )

  await SplitTestHandler.promises.getAssignment(
    req,
    res,
    'themed-project-dashboard'
  )

  res.render(Path.resolve(__dirname, '../views/manage-projects-react'), {
    title: 'Manage Projects',
    userSettings,
    prefetchedProjectsBlob,
  })
}

async function getProjectsJson(req, res) {
  const { filters, page, sort } = req.body
  const { userId } = req.params

  const projectsPage = await _getProjects(userId, filters, sort, page)
  res.json(projectsPage)
}

async function _getProjects(
  userId = null,
  filters = {},
  sort = { by: 'lastUpdated', order: 'desc' },
  page = { size: 20 }
) {

  const projection = {
    _id: 1,
    name: 1,
    lastUpdated: 1,
    lastUpdatedBy: 1,
    lastOpened: 1,
    trashed: 1,
    owner_ref: 1,
  }

  const actualProjects = await Project.find(
    userId == null ? {} : { owner_ref: userId },
    projection,
  ).lean().exec()

  const delProjection = Object.fromEntries(
    Object.keys(projection).map(k => [`project.${k}`, 1])
  )
  delProjection['deleterData.deletedAt'] = 1
  delProjection['deleterData.deleterId'] = 1

  const deletedProjects = await DeletedProject.find(
    userId == null ? { project: { $type: 'object' } } : { 'project.owner_ref': userId },
    delProjection
  ).lean().exec()

  const formattedActualProjects = _formatProjects(actualProjects, _formatProjectInfo)
  const formattedDeletedProjects = _formatProjects(deletedProjects, _formatDeletedProjectInfo)
  const formattedProjects = [...formattedActualProjects, ...formattedDeletedProjects]
  const filteredProjects = _applyFilters(formattedProjects, filters)
  const projects = _sortAndPaginate(filteredProjects, sort, page)

  return {
    totalSize: filteredProjects.length,
    projects,
  }
}

function _formatProjects(projects, formatProjectInfo) {
  const yearAgo = new Date()
  yearAgo.setFullYear(yearAgo.getFullYear() - 1)
  const formattedProjects = []

  for (const project of projects) {
    formattedProjects.push(
      formatProjectInfo(project, yearAgo)
    )
  }
  return formattedProjects
}

function _applyFilters(projects,  filters) {
  if (!_hasActiveFilter(filters)) {
    return projects
  }
  return projects.filter(project => _matchesFilters(project, filters))
}

function _sortAndPaginate(projects, sort, page) {
  if (
    (sort.by && !['lastUpdated', 'title', 'deletedAt', 'owner'].includes(sort.by)) ||
    (sort.order && !['asc', 'desc'].includes(sort.order))
  ) {
    throw new OError('Invalid sorting criteria', { sort })
  }

// sorting by owner is not implemented, it is not needed
  const sortedProjects =
    sort.by === 'title'
      ? [...projects].sort((a, b) =>
          (a.name ?? '\uffff').localeCompare(b.name ?? '\uffff')
        )
      : _.orderBy(
          projects,
          [sort.by || 'lastUpdated'],
          [sort.order || 'desc']
        )
  return sortedProjects
}

function _formatProjectInfo(project, maxDate) {
  const owner_ref = project.owner_ref
  const trashed = owner_ref ? ProjectHelper.isTrashed(project, owner_ref) : false

  return {
    id: project._id.toString(),
    name: project.name,
    owner: project.owner_ref,
    lastUpdated: project.lastUpdated?.toISOString(),
    lastUpdatedBy: project.lastUpdatedBy,
    inactive: project.lastOpened < maxDate, 
    trashed,
    deleted: false,
  }
}

function _formatDeletedProjectInfo(deletedProject, maxDate) {
  const project = deletedProject.project
  const owner_ref = project.owner_ref
  const trashed = owner_ref ? ProjectHelper.isTrashed(project, owner_ref) : false

  return {
    id: project._id.toString(),
    name: project.name,
    owner: owner_ref,
    lastUpdated: project.lastUpdated?.toISOString(),
    lastUpdatedBy: project.lastUpdatedBy,
    inactive: project.lastOpened < maxDate,
    trashed,
    deleted: true,
    deletedAt: deletedProject.deleterData?.deletedAt?.toISOString(),
    deleterId: deletedProject.deleterData?.deleterId,
  }
}

function _matchesFilters(project, filters) {
  if (filters.owned && (project.trashed || project.deleted)) {
    return false
  }
  if (filters.trashed && (!project.trashed || project.deleted)) {
    return false
  }
  if (filters.deleted && !project.deleted) {
    return false
  }
  if (filters.inactive && (project.trashed || project.deleted || !project.inactive)) {
    return false
  }
  if (
    filters.search?.length &&
    project.name.toLowerCase().indexOf(filters.search.toLowerCase()) === -1
  ) {
    return false
  }
  return true
}

function _hasActiveFilter(filters) {
  return Boolean(
    filters.owned ||
      filters.inactive ||
      filters.trashed ||
      filters.deleted ||
      filters.search?.length
  )
}

async function trashProjectForUser(req, res) {
  const projectId = req.params.project_id
  const { userId } = req.body
  await ProjectDeleter.promises.trashProject(projectId, userId)
  res.sendStatus(200)
}

async function untrashProjectForUser(req, res) {
  const projectId = req.params.project_id
  const { userId } = req.body
  await ProjectDeleter.promises.untrashProject(projectId, userId)
  res.sendStatus(200)
}

async function deleteProject(req, res) {
  const projectId = req.params.project_id
  const deleterId = SessionManager.getLoggedInUserId(req.session)
  const options = { deleterUser: {_id: deleterId } }
  const deletedProjectData = await ProjectDeleter.promises.deleteProject(projectId, options)
  return res.json(deletedProjectData)
}

async function undeleteProject(req, res) {
  const projectId = req.params.project_id
  const { userId } = req.body
  const undelededProject = await ProjectDeleter.promises.undeleteProject(projectId, { userId })
  await ProjectDeleter.promises.untrashProject(projectId, userId)

  return res.json({
    name: undelededProject.name,
  })
}

async function purgeDeletedProject(req, res) {
  const projectId = req.params.project_id
  await ProjectDeleter.promises.expireDeletedProject(projectId)
  res.sendStatus(200)
}

export default {
  manageProjectsPage: expressify(manageProjectsPage),
  getProjectsJson: expressify(getProjectsJson),
  deleteProject: expressify(deleteProject),
  undeleteProject: expressify(undeleteProject),
  purgeDeletedProject: expressify(purgeDeletedProject),
  trashProjectForUser: expressify(trashProjectForUser),
  untrashProjectForUser: expressify(untrashProjectForUser),
}
