import { IssueStatus, IssueType } from '@server/constants/issue';
import { getRepository } from '@server/datasource';
import Issue from '@server/entity/Issue';
import IssueComment from '@server/entity/IssueComment';
import Media from '@server/entity/Media';
import type { IssueResultsResponse } from '@server/interfaces/api/issueInterfaces';
import { Permission } from '@server/lib/permissions';
import logger from '@server/logger';
import { isAuthenticated } from '@server/middleware/auth';
import { Router } from 'express';

const issueRoutes = Router();

issueRoutes.get<Record<string, string>, IssueResultsResponse>(
  '/',
  isAuthenticated(
    [
      Permission.MANAGE_ISSUES,
      Permission.VIEW_ISSUES,
      Permission.CREATE_ISSUES,
    ],
    { type: 'or' }
  ),
  async (req, res, next) => {
    const pageSize = req.query.take ? Number(req.query.take) : 10;
    const skip = req.query.skip ? Number(req.query.skip) : 0;
    const createdBy = req.query.createdBy ? Number(req.query.createdBy) : null;

    let sortFilter: string;

    switch (req.query.sort) {
      case 'modified':
        sortFilter = 'issue.updatedAt';
        break;
      default:
        sortFilter = 'issue.createdAt';
    }

    let statusFilter: IssueStatus[];

    switch (req.query.filter) {
      case 'open':
        statusFilter = [IssueStatus.OPEN];
        break;
      case 'resolved':
        statusFilter = [IssueStatus.RESOLVED];
        break;
      default:
        statusFilter = [IssueStatus.OPEN, IssueStatus.RESOLVED];
    }

    let query = getRepository(Issue)
      .createQueryBuilder('issue')
      .leftJoinAndSelect('issue.createdBy', 'createdBy')
      .leftJoinAndSelect('issue.media', 'media')
      .leftJoinAndSelect('issue.modifiedBy', 'modifiedBy')
      .where('issue.status IN (:...issueStatus)', {
        issueStatus: statusFilter,
      });

    if (
      !req.user?.hasPermission(
        [Permission.MANAGE_ISSUES, Permission.VIEW_ISSUES],
        { type: 'or' }
      )
    ) {
      if (createdBy && createdBy !== req.user?.id) {
        return next({
          status: 403,
          message:
            'You do not have permission to view issues reported by other users',
        });
      }
      query = query.andWhere('createdBy.id = :id', { id: req.user?.id });
    } else if (createdBy) {
      query = query.andWhere('createdBy.id = :id', { id: createdBy });
    }

    const [issues, issueCount] = await query
      .orderBy(sortFilter, 'DESC')
      .take(pageSize)
      .skip(skip)
      .getManyAndCount();

    return res.status(200).json({
      pageInfo: {
        pages: Math.ceil(issueCount / pageSize),
        pageSize,
        results: issueCount,
        page: Math.ceil(skip / pageSize) + 1,
      },
      results: issues,
    });
  }
);

issueRoutes.post<
  Record<string, string>,
  Issue,
  {
    message: string;
    mediaId: number;
    issueType: number;
    problemSeason: number;
    problemEpisode: number;
  }
>(
  '/',
  isAuthenticated([Permission.MANAGE_ISSUES, Permission.CREATE_ISSUES], {
    type: 'or',
  }),
  async (req, res, next) => {
    // Satisfy typescript here. User is set, we assure you!
    if (!req.user) {
      return next({ status: 500, message: 'User missing from request.' });
    }

    const issueRepository = getRepository(Issue);
    const mediaRepository = getRepository(Media);

    const media = await mediaRepository.findOne({
      where: { id: req.body.mediaId },
    });

    if (!media) {
      return next({ status: 404, message: 'Media does not exist.' });
    }

    const issue = new Issue({
      createdBy: req.user,
      issueType: req.body.issueType,
      problemSeason: req.body.problemSeason,
      problemEpisode: req.body.problemEpisode,
      media,
      comments: [
        new IssueComment({
          user: req.user,
          message: req.body.message,
        }),
      ],
    });

    const newIssue = await issueRepository.save(issue);

    return res.status(200).json(newIssue);
  }
);

issueRoutes.get('/count', async (req, res, next) => {
  const issueRepository = getRepository(Issue);

  try {
    const query = issueRepository.createQueryBuilder('issue');

    const totalCount = await query.getCount();

    const videoCount = await query
      .where('issue.issueType = :issueType', {
        issueType: IssueType.VIDEO,
      })
      .getCount();

    const audioCount = await query
      .where('issue.issueType = :issueType', {
        issueType: IssueType.AUDIO,
      })
      .getCount();

    const subtitlesCount = await query
      .where('issue.issueType = :issueType', {
        issueType: IssueType.SUBTITLES,
      })
      .getCount();

    const othersCount = await query
      .where('issue.issueType = :issueType', {
        issueType: IssueType.OTHER,
      })
      .getCount();

    const openCount = await query
      .where('issue.status = :issueStatus', {
        issueStatus: IssueStatus.OPEN,
      })
      .getCount();

    const closedCount = await query
      .where('issue.status = :issueStatus', {
        issueStatus: IssueStatus.RESOLVED,
      })
      .getCount();

    return res.status(200).json({
      total: totalCount,
      video: videoCount,
      audio: audioCount,
      subtitles: subtitlesCount,
      others: othersCount,
      open: openCount,
      closed: closedCount,
    });
  } catch (e) {
    logger.debug('Something went wrong retrieving issue counts.', {
      label: 'API',
      errorMessage: e.message,
    });
    next({ status: 500, message: 'Unable to retrieve issue counts.' });
  }
});

issueRoutes.get<{ issueId: string }>(
  '/:issueId',
  isAuthenticated(
    [
      Permission.MANAGE_ISSUES,
      Permission.VIEW_ISSUES,
      Permission.CREATE_ISSUES,
    ],
    { type: 'or' }
  ),
  async (req, res, next) => {
    const issueRepository = getRepository(Issue);
    // Satisfy typescript here. User is set, we assure you!
    if (!req.user) {
      return next({ status: 500, message: 'User missing from request.' });
    }

    try {
      const issue = await issueRepository
        .createQueryBuilder('issue')
        .leftJoinAndSelect('issue.comments', 'comments')
        .leftJoinAndSelect('issue.createdBy', 'createdBy')
        .leftJoinAndSelect('comments.user', 'user')
        .leftJoinAndSelect('issue.media', 'media')
        .where('issue.id = :issueId', { issueId: Number(req.params.issueId) })
        .getOneOrFail();

      if (
        issue.createdBy.id !== req.user.id &&
        !req.user.hasPermission(
          [Permission.MANAGE_ISSUES, Permission.VIEW_ISSUES],
          { type: 'or' }
        )
      ) {
        return next({
          status: 403,
          message: 'You do not have permission to view this issue.',
        });
      }

      return res.status(200).json(issue);
    } catch (e) {
      logger.debug('Failed to retrieve issue.', {
        label: 'API',
        errorMessage: e.message,
      });
      next({ status: 500, message: 'Issue not found.' });
    }
  }
);

issueRoutes.post<{ issueId: string }, Issue, { message: string }>(
  '/:issueId/comment',
  isAuthenticated([Permission.MANAGE_ISSUES, Permission.CREATE_ISSUES], {
    type: 'or',
  }),
  async (req, res, next) => {
    const issueRepository = getRepository(Issue);
    // Satisfy typescript here. User is set, we assure you!
    if (!req.user) {
      return next({ status: 500, message: 'User missing from request.' });
    }

    try {
      const issue = await issueRepository.findOneOrFail({
        where: { id: Number(req.params.issueId) },
      });

      if (
        issue.createdBy.id !== req.user.id &&
        !req.user.hasPermission(Permission.MANAGE_ISSUES)
      ) {
        return next({
          status: 403,
          message: 'You do not have permission to comment on this issue.',
        });
      }

      const comment = new IssueComment({
        message: req.body.message,
        user: req.user,
      });

      issue.comments = [...issue.comments, comment];

      await issueRepository.save(issue);

      return res.status(200).json(issue);
    } catch (e) {
      logger.debug('Something went wrong creating an issue comment.', {
        label: 'API',
        errorMessage: e.message,
      });
      next({ status: 500, message: 'Issue not found.' });
    }
  }
);

issueRoutes.post<{ issueId: string; status: string }, Issue>(
  '/:issueId/:status',
  isAuthenticated(Permission.MANAGE_ISSUES),
  async (req, res, next) => {
    const issueRepository = getRepository(Issue);
    // Satisfy typescript here. User is set, we assure you!
    if (!req.user) {
      return next({ status: 500, message: 'User missing from request.' });
    }

    try {
      const issue = await issueRepository.findOneOrFail({
        where: { id: Number(req.params.issueId) },
      });

      let newStatus: IssueStatus | undefined;

      switch (req.params.status) {
        case 'resolved':
          newStatus = IssueStatus.RESOLVED;
          break;
        case 'open':
          newStatus = IssueStatus.OPEN;
      }

      if (!newStatus) {
        return next({
          status: 400,
          message: 'You must provide a valid status',
        });
      }

      issue.status = newStatus;
      issue.modifiedBy = req.user;

      await issueRepository.save(issue);

      return res.status(200).json(issue);
    } catch (e) {
      logger.debug('Something went wrong creating an issue comment.', {
        label: 'API',
        errorMessage: e.message,
      });
      next({ status: 500, message: 'Issue not found.' });
    }
  }
);

issueRoutes.delete(
  '/:issueId',
  isAuthenticated([Permission.MANAGE_ISSUES, Permission.CREATE_ISSUES], {
    type: 'or',
  }),
  async (req, res, next) => {
    const issueRepository = getRepository(Issue);

    try {
      const issue = await issueRepository.findOneOrFail({
        where: { id: Number(req.params.issueId) },
        relations: { createdBy: true },
      });

      if (
        !req.user?.hasPermission(Permission.MANAGE_ISSUES) &&
        (issue.createdBy.id !== req.user?.id || issue.comments.length > 1)
      ) {
        return next({
          status: 401,
          message: 'You do not have permission to delete this issue.',
        });
      }

      await issueRepository.remove(issue);

      return res.status(204).send();
    } catch (e) {
      logger.error('Something went wrong deleting an issue.', {
        label: 'API',
        errorMessage: e.message,
      });
      next({ status: 404, message: 'Issue not found.' });
    }
  }
);

export default issueRoutes;