/**
 *  Indication Controller
 *
 *  Copyright (C) 2010-2020 R-T Specialty, LLC.
 *
 *  This file is part of the Liza Data Collection Framework.
 *
 *  liza is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

import {AsyncQuoteFactory} from '../quote/QuoteFactory';
import {EventEmitter} from 'events';
import {ProgramFactory} from '../program/ProgramFactory';
import {Program} from '../../program/Program';
import {Request, Response} from 'express';
import {ServerSideQuote} from '../../server/quote/ServerSideQuote';
import {ts_ctor} from '../../numeric';
import {
  AsyncRater,
  getDummySession,
  RateError,
  validate_session_template,
} from '../program/RaterFactory';
import {
  asyncServerDao,
  DocumentData,
  MongoServerDao,
  ServerDaoSaveError,
} from '../../server/db/MongoServerDao';
import {
  HttpBadResponseError,
  HttpClient,
  InvalidUrlError,
} from '../../system/network/HttpClient';

/* eslint-disable @typescript-eslint/no-var-requires */
const createQuote = require('../../server/quote/ServerSideQuote');
const createBucket = require('../../bucket/QuoteDataBucket');
/* eslint-enable @typescript-eslint/no-var-requires */

export const indication = {
  /**
   * Create a new indication
   *
   * @param emitter         - event emitter
   * @param http            - HTTP client
   * @param program_factory - used to generate programs on demand
   * @param quote_factory   - create quotes
   * @param request         - request object
   * @param response        - response object
   */
  create: (emitter: EventEmitter) => (http: HttpClient) => (
    program_factory: ProgramFactory
  ) => (quote_factory: AsyncQuoteFactory) => (rater: AsyncRater) => (
    dao: MongoServerDao
  ) => async (request: Request, response: Response) => {
    let webhook = '';
    const {
      bucket: request_bucket,
      session: request_session,
      metabucket: meta,
    } = request.body;

    if (request.query && request.query.callback) {
      webhook = decodeURIComponent((<any>request.query).callback);
    }

    try {
      http.validateUrl(webhook);
      validate_session_template(request_session);
    } catch (e) {
      const message =
        e instanceof InvalidUrlError
          ? 'Request must contain a valid webhook URL.'
          : e.message;

      response.status(422).send({message});

      emitter.emit('error', e);
      return;
    }

    // Send acceptance back to originator
    response.status(202).send({message: 'Request received.'});

    emitter.emit('response-sent', {http_code: 202});

    const session = getDummySession(request_session);

    const quote_maker = (program: Program) => (id: number): ServerSideQuote => {
      const quote = createQuote(id, createBucket(), program);
      quote.setMetabucket(createBucket());
      quote.setRateBucket(createBucket());

      return quote;
    };

    const doc_data: DocumentData = {
      agentId: session.agentId(),
      agentName: session.agentName(),
      agentEntityId: session.agentEntityId(),
      startDate: ts_ctor(),
    };

    try {
      const {bucket, program} = program_factory.createProgram(request_bucket);

      const quote = (
        await quote_factory(dao)(quote_maker)(program)(session)(doc_data)
      ).setData(bucket.getData());

      const rdata = await rater.rate(quote, session);

      const delta = {
        'rdelta.data': {
          data: {},
          concluding_save: false,
          timestamp: ts_ctor(),
          step_id: -1,
        },
      };

      const data = {
        programId: program.getId(),
        // TODO: rate_pending can be automatically set when RatingService is used
        'ratedata.__rate_pending': [0],
        data: bucket.getData(),
      };

      await asyncServerDao(dao).saveQuote(quote, data, delta);
      await asyncServerDao(dao).saveQuoteMetaDirect(quote, meta ?? {});

      emitter.emit('quote-save-success', {quote_id: quote.getId()});
      emitter.emit('callback-sent', {target: webhook});

      const webhook_response = await http.post(webhook, {
        bucket: quote.getBucket().getData(),
        rdata,
      });

      if (!webhook_response.ok) {
        throw new HttpBadResponseError(
          `Webhook response returned error code: ${response.status}`
        );
      }

      emitter.emit('callback-success', {response: webhook_response});
    } catch (e) {
      if (e instanceof RateError) {
        emitter.emit('rate-error', {msg: e.message});
      } else if (e instanceof ServerDaoSaveError) {
        emitter.emit('quote-save-failure', {quote_id: e.id});
      } else if (e instanceof HttpBadResponseError) {
        emitter.emit('callback-failure', {error: e});
      } else {
        emitter.emit('error', e);
      }
    }

    return;
  },
};
