import { Service, Inject } from 'typedi';
import { Request, Response, Router, NextFunction } from 'express';
import { query, check, param } from 'express-validator';
import BaseController from '@/api/controllers/BaseController';
import { WarehouseTransferApplication } from '@/services/Warehouses/WarehousesTransfers/WarehouseTransferApplication';
import {
  Features,
  ICreateWarehouseTransferDTO,
  IEditWarehouseTransferDTO,
} from '@/interfaces';
import { ServiceError } from '@/exceptions';
import { FeatureActivationGuard } from '@/api/middleware/FeatureActivationGuard';

@Service()
export class WarehousesTransfers extends BaseController {
  @Inject()
  private warehouseTransferApplication: WarehouseTransferApplication;

  /**
   *
   */
  router() {
    const router = Router();

    router.post(
      '/',
      FeatureActivationGuard(Features.WAREHOUSES),
      [
        check('from_warehouse_id').exists().isInt().toInt(),
        check('to_warehouse_id').exists().isInt().toInt(),

        check('date').exists().isISO8601(),
        check('transaction_number').optional(),

        check('transfer_initiated').default(false).isBoolean().toBoolean(),
        check('transfer_delivered').default(false).isBoolean().toBoolean(),

        check('entries').exists().isArray({ min: 1 }),
        check('entries.*.index').exists(),
        check('entries.*.item_id').exists(),
        check('entries.*.description').optional(),
        check('entries.*.quantity').exists().isInt().toInt(),
        check('entries.*.cost').optional().isDecimal().toFloat(),
      ],
      this.validationResult,
      this.asyncMiddleware(this.createWarehouseTransfer),
      this.handlerServiceErrors
    );
    router.post(
      '/:id',
      FeatureActivationGuard(Features.WAREHOUSES),
      [
        param('id').exists().isInt().toInt(),

        check('from_warehouse_id').exists().isInt().toInt(),
        check('to_warehouse_id').exists().isInt().toInt(),

        check('date').exists().isISO8601(),
        check('transaction_number').optional(),

        check('transfer_initiated').default(false).isBoolean().toBoolean(),
        check('transfer_delivered').default(false).isBoolean().toBoolean(),

        check('entries').exists().isArray({ min: 1 }),
        check('entries.*.id').optional().isInt().toInt(),
        check('entries.*.index').exists(),
        check('entries.*.item_id').exists().isInt().toInt(),
        check('entries.*.description').optional(),
        check('entries.*.quantity').exists().isInt({ min: 1 }).toInt(),
        check('entries.*.cost').optional().isDecimal().toFloat(),
      ],
      this.validationResult,
      this.asyncMiddleware(this.editWarehouseTransfer),
      this.handlerServiceErrors
    );
    router.put(
      '/:id/initiate',
      FeatureActivationGuard(Features.WAREHOUSES),
      [param('id').exists().isInt().toInt()],
      this.asyncMiddleware(this.initiateTransfer),
      this.handlerServiceErrors
    );
    router.put(
      '/:id/transferred',
      FeatureActivationGuard(Features.WAREHOUSES),
      [param('id').exists().isInt().toInt()],
      this.asyncMiddleware(this.deliverTransfer),
      this.handlerServiceErrors
    );
    router.get(
      '/',
      FeatureActivationGuard(Features.WAREHOUSES),
      [
        query('view_slug').optional({ nullable: true }).isString().trim(),

        query('stringified_filter_roles').optional().isJSON(),
        query('column_sort_by').optional(),
        query('sort_order').optional().isIn(['desc', 'asc']),

        query('page').optional().isNumeric().toInt(),
        query('page_size').optional().isNumeric().toInt(),

        query('search_keyword').optional({ nullable: true }).isString().trim(),
      ],
      this.validationResult,
      this.asyncMiddleware(this.getWarehousesTransfers),
      this.handlerServiceErrors
    );
    router.get(
      '/:id',
      [param('id').exists().isInt().toInt()],
      this.validationResult,
      this.asyncMiddleware(this.getWarehouseTransfer),
      this.handlerServiceErrors
    );
    router.delete(
      '/:id',
      FeatureActivationGuard(Features.WAREHOUSES),
      [param('id').exists().isInt().toInt()],
      this.validationResult,
      this.asyncMiddleware(this.deleteWarehouseTransfer),
      this.handlerServiceErrors
    );
    return router;
  }

  /**
   * Creates a new warehouse transfer transaction.
   * @param   {Request} req
   * @param   {Response} res
   * @param   {NextFunction} next
   * @returns {Response}
   */
  private createWarehouseTransfer = async (
    req: Request,
    res: Response,
    next: NextFunction
  ) => {
    const { tenantId } = req;
    const createWareouseTransfer: ICreateWarehouseTransferDTO =
      this.matchedBodyData(req);

    try {
      const warehouse =
        await this.warehouseTransferApplication.createWarehouseTransfer(
          tenantId,
          createWareouseTransfer
        );
      return res.status(200).send({
        id: warehouse.id,
        message:
          'The warehouse transfer transaction has been created successfully.',
      });
    } catch (error) {
      next(error);
    }
  };

  /**
   * Edits warehouse transfer transaction.
   * @param   {Request} req
   * @param   {Response} res
   * @param   {NextFunction} next
   * @returns {Response}
   */
  private editWarehouseTransfer = async (
    req: Request,
    res: Response,
    next: NextFunction
  ) => {
    const { tenantId } = req;
    const { id: warehouseTransferId } = req.params;
    const editWarehouseTransferDTO: IEditWarehouseTransferDTO =
      this.matchedBodyData(req);

    try {
      const warehouseTransfer =
        await this.warehouseTransferApplication.editWarehouseTransfer(
          tenantId,
          warehouseTransferId,
          editWarehouseTransferDTO
        );
      return res.status(200).send({
        id: warehouseTransfer.id,
        message:
          'The warehouse transfer transaction has been edited successfully.',
      });
    } catch (error) {
      next(error);
    }
  };

  /**
   * Deletes the given warehouse transfer transaction.
   * @param   {Request} req
   * @param   {Response} res
   * @param   {NextFunction} next
   * @returns {Response}
   */
  private deleteWarehouseTransfer = async (
    req: Request,
    res: Response,
    next: NextFunction
  ) => {
    const { tenantId } = req;
    const { id: warehouseTransferId } = req.params;

    try {
      await this.warehouseTransferApplication.deleteWarehouseTransfer(
        tenantId,
        warehouseTransferId
      );
      return res.status(200).send({
        message:
          'The warehouse transfer transaction has been deleted successfully.',
      });
    } catch (error) {
      next(error);
    }
  };

  /**
   * Retrieves warehouse transfer transaction details.
   * @param   {Request} req
   * @param   {Response} res
   * @param   {NextFunction} next
   * @returns {Response}
   */
  private getWarehouseTransfer = async (
    req: Request,
    res: Response,
    next: NextFunction
  ) => {
    const { tenantId } = req;
    const { id: warehouseTransferId } = req.params;

    try {
      const warehouseTransfer =
        await this.warehouseTransferApplication.getWarehouseTransfer(
          tenantId,
          warehouseTransferId
        );
      return res.status(200).send({ data: warehouseTransfer });
    } catch (error) {
      next(error);
    }
  };

  /**
   * Retrieves specific warehouse transfer transaction.
   * @param   {Request} req
   * @param   {Response} res
   * @param   {NextFunction} next
   * @returns {Response}
   */
  private getWarehousesTransfers = async (
    req: Request,
    res: Response,
    next: NextFunction
  ) => {
    const { tenantId } = req;
    const filterDTO = {
      sortOrder: 'desc',
      columnSortBy: 'created_at',
      page: 1,
      pageSize: 12,
      ...this.matchedQueryData(req),
    };
    try {
      const { warehousesTransfers, pagination, filter } =
        await this.warehouseTransferApplication.getWarehousesTransfers(
          tenantId,
          filterDTO
        );

      return res.status(200).send({
        data: warehousesTransfers,
        pagination,
        filter,
      });
    } catch (error) {
      next(error);
    }
  };

  /**
   * Initiates the warehouse transfer.
   * @param   {Request} req
   * @param   {Response} res
   * @param   {NextFunction} next
   * @returns {Response}
   */
  private initiateTransfer = async (
    req: Request,
    res: Response,
    next: NextFunction
  ) => {
    const { tenantId } = req;
    const { id: warehouseTransferId } = req.params;

    try {
      await this.warehouseTransferApplication.initiateWarehouseTransfer(
        tenantId,
        warehouseTransferId
      );
      return res.status(200).send({
        id: warehouseTransferId,
        message: 'The given warehouse transfer has been initialized.',
      });
    } catch (error) {
      next(error);
    }
  };

  /**
   * marks the given warehouse transfer as transferred.
   * @param   {Request} req
   * @param   {Response} res
   * @param   {NextFunction} next
   * @returns {Response}
   */
  private deliverTransfer = async (
    req: Request,
    res: Response,
    next: NextFunction
  ) => {
    const { tenantId } = req;
    const { id: warehouseTransferId } = req.params;

    try {
      await this.warehouseTransferApplication.transferredWarehouseTransfer(
        tenantId,
        warehouseTransferId
      );
      return res.status(200).send({
        id: warehouseTransferId,
        message: 'The given warehouse transfer has been delivered.',
      });
    } catch (error) {
      next(error);
    }
  };

  /**
   * Handles service errors.
   * @param {Error} error
   * @param {Request} req
   * @param {Response} res
   * @param {NextFunction} next
   */
  private handlerServiceErrors(
    error: Error,
    req: Request,
    res: Response,
    next: NextFunction
  ) {
    if (error instanceof ServiceError) {
      if (error.errorType === 'WAREHOUSES_TRANSFER_SHOULD_NOT_BE_SAME') {
        return res.status(400).send({
          errors: [
            { type: 'WAREHOUSES_TRANSFER_SHOULD_NOT_BE_SAME', code: 100 },
          ],
        });
      }
      if (error.errorType === 'FROM_WAREHOUSE_NOT_FOUND') {
        return res.status(400).send({
          errors: [{ type: 'FROM_WAREHOUSE_NOT_FOUND', code: 200 }],
        });
      }
      if (error.errorType === 'TO_WAREHOUSE_NOT_FOUND') {
        return res.status(400).send({
          errors: [{ type: 'TO_WAREHOUSE_NOT_FOUND', code: 300 }],
        });
      }
      if (error.errorType === 'ITEMS_NOT_FOUND') {
        return res.status(400).send({
          errors: [{ type: 'ITEMS_NOT_FOUND', code: 400 }],
        });
      }
      if (error.errorType === 'WAREHOUSE_TRANSFER_ITEMS_SHOULD_BE_INVENTORY') {
        return res.status(400).send({
          errors: [
            { type: 'WAREHOUSE_TRANSFER_ITEMS_SHOULD_BE_INVENTORY', code: 500 },
          ],
        });
      }
      if (error.errorType === 'WAREHOUSE_TRANSFER_ALREADY_TRANSFERRED') {
        return res.status(400).send({
          errors: [
            { type: 'WAREHOUSE_TRANSFER_ALREADY_TRANSFERRED', code: 600 },
          ],
        });
      }
      if (error.errorType === 'WAREHOUSE_TRANSFER_ALREADY_INITIATED') {
        return res.status(400).send({
          errors: [{ type: 'WAREHOUSE_TRANSFER_ALREADY_INITIATED', code: 700 }],
        });
      }
      if (error.errorType === 'WAREHOUSE_TRANSFER_NOT_INITIATED') {
        return res.status(400).send({
          errors: [{ type: 'WAREHOUSE_TRANSFER_NOT_INITIATED', code: 800 }],
        });
      }
    }
    next(error);
  }
}
