text: const Router = require('koa-router');
  const logger = require('logger');
  const ReportsSerializer = require('serializers/reportsSerializer');
  const ReportsModel = require('models/reportsModel');
  const ReportsValidator = require('validators/reportsValidator');
  const AnswersModel = require('models/answersModel');
  const AnswersService = require('services/answersService');
  const TeamService = require('services/teamService');
  const passThrough = require('stream').PassThrough;
  const { ObjectId } = require('mongoose').Types;
  const config = require('config');
  const { RWAPIMicroservice } = require('rw-api-microservice-node');
  const CSV = require('services/csvService');
  
  const router = new Router({
      prefix: '/reports'
  });
  
  class ReportsRouter {
  
      static* getAll() {
          logger.info('Obtaining all reports');
          const filter = {
              $and: [
                  {
                      $or: [
                          { $and: [{ public: true }, { status: 'published' }] },
                          { user: new ObjectId(this.state.loggedUser.id) }]
                  }
              ]
          };
          if (this.state.query) {
              Object.keys(this.state.query).forEach((key) => {
                  filter.$and.push({ [key]: this.state.query[key] });
              });
          }
          const reports = yield ReportsModel.find(filter);
  
          // get answer count for each report
          const numReports = reports.length;
          for (let i = 1; i < numReports; i++) {
              let answersFilter = {};
              if (this.state.loggedUser.role === 'ADMIN' || this.state.loggedUser.id === reports[i].user) {
                  answersFilter = {
                      report: new ObjectId(reports[i].id)
                  };
              } else {
                  answersFilter = {
                      user: new ObjectId(this.state.loggedUser.id),
                      report: new ObjectId(reports[i].id)
                  };
              }
              const answers = yield AnswersModel.count(answersFilter);
              logger.info(answers);
              reports[i].answersCount = answers || 0;
          }
  
          this.body = ReportsSerializer.serialize(reports);
      }
  
      static* get() {
          logger.info(`Obtaining reports with id ${this.params.id}`);
          const report = yield ReportsModel.findOne({ _id: this.params.id });
          if (!report) {
              this.throw(404, 'Report not found');
              return;
          }
  
          // get answers count for the report
          let answersFilter = {};
          if (this.state.loggedUser.role === 'ADMIN' || this.state.loggedUser.id === report.user) {
              answersFilter = {
                  report: new ObjectId(this.params.id)
              };
          } else {
              answersFilter = {
                  user: new ObjectId(this.state.loggedUser.id),
                  report: new ObjectId(this.params.id)
              };
          }
          const answers = yield AnswersModel.count(answersFilter);
          report.answersCount = answers;
  
          this.body = ReportsSerializer.serialize(report);
      }
  
      static* save() {
          logger.info('Saving reports', this.request.body);
          const request = this.request.body;
  
          if (request.public && this.state.loggedUser.role !== 'ADMIN') {
              this.throw(403, 'Admin permissions required to save public templates');
              return;
          }
  
          const report = yield new ReportsModel({
              name: request.name,
              user: this.state.loggedUser.id,
              languages: request.languages,
              defaultLanguage: request.defaultLanguage,
              questions: request.questions,
              public: request.public,
              status: request.status
          }).save();
  
          // PATCH templateId onto area
          // Remove report if PATCH fails
          if (request.areaOfInterest) {
              const reportId = report._id.toString();
              try {
                  yield RWAPIMicroservice.requestToMicroservice({
                      uri: `/area/${request.areaOfInterest}`,
                      method: 'PATCH',
                      json: true,
                      body: {
                          templateId: reportId,
                          userId: this.state.loggedUser.id
                      }
                  });
              } catch (e) {
                  yield ReportsModel.remove({ _id: reportId });
                  logger.error('request to microservice failed');
                  logger.error(e);
                  this.throw(500, 'Error creating templates: patch to area failed');
                  return;
              }
          }
  
          this.body = ReportsSerializer.serialize(report);
      }
  
      static* put() {
          logger.info('Updating report', this.request.body);
          const { body } = this.request;
  
          if (this.state.loggedUser.role !== 'ADMIN') {
              this.throw(403, 'Only admins can update reports.');
              return;
          }
  
          const report = yield ReportsModel.findOne({ _id: new ObjectId(this.params.id) });
  
          if (!report) {
              this.throw(404, 'Report not found with these permissions');
              return;
          }
  
          Object.assign(report, body);
  
          // add answers count to return and updated date
          const answers = yield AnswersModel.count({ report: new ObjectId(this.params.id) });
          report.answersCount = answers;
          report.updatedDate = Date.now;
  
          yield report.save();
          this.body = ReportsSerializer.serialize(report);
      }
  
      static* patch() {
          logger.info(`Updating template with id ${this.params.id}...`);
  
          const reportFilter = {
              $and: [
                  { _id: new ObjectId(this.params.id) }
              ]
          };
          if (this.state.loggedUser.role !== 'ADMIN') {
              reportFilter.$and.push({ user: new ObjectId(this.state.loggedUser.id) });
          }
          const report = yield ReportsModel.findOne(reportFilter);
          const request = this.request.body;
  
          // if user did not create then return error
          if (!report) {
              this.throw(404, 'Report not found.');
              return;
          }
  
          // props allow to change even with answers
          if (request.name) {
              report.name = request.name;
          }
  
          if (request.status) {
              report.status = request.status;
          }
  
          if (request.languages) {
              report.languages = request.languages;
          }
  
          // if user is an admin, they can make the report public
          if (this.state.loggedUser.role === 'ADMIN' && request.public) {
              report.public = request.public;
          }
  
          // PATCH templateId onto area
          // Remove report if PATCH fails
          if (request.areaOfInterest !== request.oldAreaOfInterest) {
  
              // remove old area
              if (request.oldAreaOfInterest) {
                  logger.info(`PATCHing old area of interest ${request.oldAreaOfInterest}...`);
                  try {
                      yield RWAPIMicroservice.requestToMicroservice({
                          uri: `/area/${request.oldAreaOfInterest}`,
                          method: 'PATCH',
                          json: true,
                          body: {
                              templateId: null,
                              userId: this.state.loggedUser.id
                          }
                      });
                  } catch (e) {
                      logger.error(e);
                      this.throw(500, 'PATCHing old area failed');
                      return;
                  }
              }
  
              // PATCH new area
              if (request.areaOfInterest) {
                  logger.info(`PATCHing new area of interest ${request.oldAreaOfInterest}...`);
                  try {
                      yield RWAPIMicroservice.requestToMicroservice({
                          uri: `/area/${request.areaOfInterest}`,
                          method: 'PATCH',
                          json: true,
                          body: {
                              templateId: this.params.id,
                              userId: this.state.loggedUser.id
                          }
                      });
                  } catch (e) {
                      logger.error(e);
                      this.throw(500, 'PATCHing new area failed');
                      return;
                  }
              }
          }
  
          // add answers count to return and updated date
          const answers = yield AnswersModel.count({ report: new ObjectId(this.params.id) });
          report.answersCount = answers;
          report.updatedDate = Date.now;
  
          yield report.save();
          this.body = ReportsSerializer.serialize(report);
      }
  
      static* delete() {
          const { role } = this.state.loggedUser;
          const aoi = this.state.query && this.state.query.aoi !== null ? this.state.query.aoi.split(',') : null;
          logger.info(`Checking report for answers...`);
          const answers = yield AnswersModel.count({ report: new ObjectId(this.params.id) });
          if (answers > 0 && role !== 'ADMIN') {
              this.throw(403, 'This report has answers, you cannot delete. Please unpublish instead.');
              return;
          }
          logger.info(`Report has no answers.`);
          logger.info(`Deleting report with id ${this.params.id}...`);
          if (aoi !== null) {
              for (let i = 0; i < aoi.length; i++) {
                  logger.info(`PATCHing area ${aoi[i]} to remove template association...`);
                  try {
                      yield RWAPIMicroservice.requestToMicroservice({
                          uri: `/area/${aoi[i]}`,
                          method: 'PATCH',
                          json: true,
                          body: {
                              templateId: null,
                              userId: this.state.loggedUser.id
                          }
                      });
                      logger.info(`Area ${aoi[i]} patched.`);
                  } catch (e) {
                      logger.error(e);
                      this.throw(500, e);
                      return;
                  }
              }
              logger.info('Areas patched. Removing template...');
          }
  
          // finally remove template
          const query = {
              $and: [
                  { _id: new ObjectId(this.params.id) }
              ]
          };
          if (role !== 'ADMIN') {
              query.$and.push({ user: new ObjectId(this.state.loggedUser.id) });
              query.$and.push({ status: ['draft', 'unpublished'] });
          } else if (answers > 0) {
              logger.info('User is admin, removing report answers...');
              yield AnswersModel.remove({ report: new ObjectId(this.params.id) });
          }
          const result = yield ReportsModel.remove(query);
  
          if (!result || !result.result || result.result.ok === 0) {
              this.throw(404, 'Report not found with these permissions. You must be the owner to remove.');
              return;
          }
          this.statusCode = 204;
      }
  
      static* downloadAnswers() {
          logger.info(`Downloading answers for report ${this.params.id}`);
          this.set('Content-disposition', `attachment; filename=${this.params.id}.csv`);
          this.set('Content-type', 'text/csv');
          this.body = passThrough();
  
          let report = yield ReportsModel.findOne({
              $and: [
                  { _id: new ObjectId(this.params.id) },
                  { $or: [{ public: true }, { user: new ObjectId(this.state.loggedUser.id) }] }
              ]
          });
          if (!report) {
              this.throw(404, 'Report not found');
              return;
          }
  
          report = report.toObject();
  
          const questionLabels = report.questions.reduce((acc, question) => ({
              ...acc,
              [question.name]: question.label[report.defaultLanguage],
              ...question.childQuestions.reduce((acc2, childQuestion) => ({
                  ...acc2,
                  [childQuestion.name]: childQuestion.label[report.defaultLanguage]
              }), {})
          }), {
              userId: 'User',
              reportName: 'Name',
              areaOfInterest: 'Area of Interest',
              areaOfInterestName: 'Area of Interest name',
              clickedPositionLat: 'Position of report lat',
              clickedPositionLon: 'Position of report lon',
              userPositionLat: 'Position of user lat',
              userPositionLon: 'Position of user lon',
              layer: 'Alert type'
          });
  
          const team = yield TeamService.getTeam(this.state.loggedUser.id);
          let teamData = null;
          if (team.data && team.data.attributes) {
              teamData = team.data.attributes;
          }
  
          const answers = yield AnswersService.getAllAnswers({
              team: teamData,
              reportId: this.params.id,
              template: report,
              query: null,
              loggedUser: this.state.loggedUser
          });
  
          logger.info('Obtaining data');
  
          const data = answers.map((answer) => answer.toObject())
              .map((answer) => {
                  const responses = {
                      userId: answer.user || null,
                      reportName: answer.reportName,
                      areaOfInterest: answer.areaOfInterest || null,
                      areaOfInterestName: answer.areaOfInterestName || null,
                      clickedPositionLat: answer.clickedPosition.length ? answer.clickedPosition[0].lat : null,
                      clickedPositionLon: answer.clickedPosition.length ? answer.clickedPosition[0].lon : null,
                      userPositionLat: answer.userPosition.length ? answer.userPosition[0] : null,
                      userPositionLon: answer.userPosition.length ? answer.userPosition[1] : null,
                  };
  
                  answer.responses.forEach((response) => {
                      const currentQuestion = { ...report.questions.find((question) => (question.name && question.name === response.name)) };
  
                      responses[response.name] = response.value;
                      if (response.value !== null && ['checkbox', 'radio', 'select'].includes(currentQuestion.type)) {
                          const getCurrentValue = (list, val) => (list.find((item) => (item.value === val || item.value === parseInt(val, 10))));
                          // eslint-disable-next-line no-restricted-globals
                          const values = !response.value.includes(',') && !isNaN(parseInt(response.value, 10)) ? [response.value] : response.value.split(',');
                          const questionValues = currentQuestion.values[report.defaultLanguage];
                          responses[response.name] = values.reduce((acc, value) => {
                              const val = getCurrentValue(questionValues, value);
                              const accString = acc !== '' ? `${acc}, ` : acc;
                              return typeof val !== 'undefined' ? `${accString}${val.label}` : `${accString}${value}`;
                          }, '');
                      }
                  });
                  return Object.entries(responses).reduce((acc, [key, value]) => {
                      const label = questionLabels[key] || key;
                      return {
                          ...acc,
                          [label]: value
                      };
                  }, {});
              });
          this.body.write(CSV.convert(data));
          this.body.end();
      }
  
  }
  
  function* mapTemplateParamToId(next) {
      if (this.params.id === config.get('legacyTemplateId') || this.params.id === 'default') {
          this.params.id = config.get('defaultTemplateId');
      }
      yield next;
  }
  
  function* loggedUserToState(next) {
      if (this.query && this.query.loggedUser) {
          this.state.loggedUser = JSON.parse(this.query.loggedUser);
          delete this.query.loggedUser;
      } else if (this.request.body && this.request.body.loggedUser) {
          this.state.loggedUser = this.request.body.loggedUser;
          delete this.request.body.loggedUser;
      } else {
          this.throw(401, 'Unauthorized');
          return;
      }
      yield next;
  }
  
  function* queryToState(next) {
      if (this.request.query && Object.keys(this.request.query).length > 0) {
          this.state.query = this.request.query;
      }
      yield next;
  }
  
  // check permission must be added at some point
  router.post('/', loggedUserToState, ReportsValidator.create, ReportsRouter.save);
  router.patch('/:id', mapTemplateParamToId, loggedUserToState, ReportsValidator.patch, ReportsRouter.patch);
  router.get('/', loggedUserToState, queryToState, ReportsRouter.getAll);
  router.get('/:id', mapTemplateParamToId, loggedUserToState, queryToState, ReportsRouter.get);
  router.put('/:id', mapTemplateParamToId, loggedUserToState, queryToState, ReportsValidator.create, ReportsRouter.put);
  router.delete('/:id', mapTemplateParamToId, loggedUserToState, queryToState, ReportsRouter.delete);
  router.get('/:id/download-answers', mapTemplateParamToId, loggedUserToState, ReportsRouter.downloadAnswers);
  
  module.exports = router;
  