// Common step definitions shared across multiple feature test scenarios
import util from 'util';
import assert from 'assert';

export default class SharedSteps {
  constructor(world) {
    this.world = world;
  }

  ShouldGetAResponse() {
    assert.equal(this.response.statusCode, 200);
    assert.ok(this.response.body);
    assert.ok(this.response.body.length);
  }

  ShouldBeValidJSON(callback) {
    try {
      this.json = JSON.parse(this.response.body);
      callback();
    } catch (e) {
      callback(e);
    }
  }

  ShouldBeWellFormed() {
    assert.equal(typeof this.json.status, 'number');
  }

  async WhenIRouteIShouldGet(table) {
    await this.reprocessAndLoadData();
    const headers = new Set(table.raw()[0]);

    const requestRow = (row, _ri) => {
      return new Promise((resolve, reject) => {
        let got;

        const afterRequest = (err, res, body) => {
          if (err) return reject(err);
          if (body && body.length) {
            let destinations,
              exits,
              pronunciations,
              instructions,
              refs,
              bearings,
              turns,
              modes,
              times,
              classes,
              distances,
              summary,
              intersections,
              lanes,
              locations,
              annotation,
              weight_name,
              weights,
              approaches,
              driving_sides;

            const json = JSON.parse(body);

            got.code = json.code;

            const hasRoute = json.code === 'Ok';

            if (hasRoute) {
              instructions = this.wayList(json.routes[0]);
              pronunciations = this.pronunciationList(json.routes[0]);
              refs = this.refList(json.routes[0]);
              destinations = this.destinationsList(json.routes[0]);
              exits = this.exitsList(json.routes[0]);
              bearings = this.bearingList(json.routes[0]);
              turns = this.turnList(json.routes[0]);
              intersections = this.intersectionList(json.routes[0]);
              modes = this.modeList(json.routes[0]);
              driving_sides = this.drivingSideList(json.routes[0]);
              classes = this.classesList(json.routes[0]);
              times = this.timeList(json.routes[0]);
              distances = this.distanceList(json.routes[0]);
              lanes = this.lanesList(json.routes[0]);
              summary = this.summary(json.routes[0]);
              locations = this.locations(json.routes[0]);
              annotation = this.annotationList(json.routes[0]);
              weight_name = this.weightName(json.routes[0]);
              weights = this.weightList(json.routes[0]);
              approaches = this.approachList(json.routes[0]);
            }

            if (headers.has('status')) {
              got.status = res.statusCode.toString();
            }

            if (headers.has('message')) {
              got.message = json.message || '';
            }

            if (headers.has('data_version')) {
              got.data_version = json.data_version || '';
            }

            if (headers.has('#')) {
              // comment column
              got['#'] = row['#'];
            }

            if (headers.has('geometry')) {
              got.geometry = json.routes[0].geometry;
            }

            if (headers.has('route')) {
              got.route = (instructions || '').trim();
            }

            if (headers.has('summary')) {
              got.summary = (summary || '').trim();
            }

            if (headers.has('alternative')) {
              // TODO examine more than first alternative?
              got.alternative = '';
              if (json.routes && json.routes.length > 1)
                got.alternative = this.wayList(json.routes[1]);
            }

            const distance = hasRoute && json.routes[0].distance,
              time = hasRoute && json.routes[0].duration,
              weight = hasRoute && json.routes[0].weight;

            if (headers.has('distance')) {
              if (row.distance.length) {
                if (!row.distance.match(/\d+m/))
                  return reject(
                    new Error(
                      '*** Distance must be specified in meters. (ex: 250m)',
                    ),
                  );
                got.distance = instructions ? util.format('%dm', distance) : '';
              } else {
                got.distance = '';
              }
            }

            if (headers.has('weight')) {
              if (row.weight.length) {
                if (!row.weight.match(/[\d.]+/))
                  return reject(
                    new Error(
                      '*** Weight must be specified as a numeric value. (ex: 8)',
                    ),
                  );
                got.weight = instructions ? util.format('%d', weight) : '';
              } else {
                got.weight = '';
              }
            }

            if (headers.has('time')) {
              if (!row.time.match(/\d+s/))
                return reject(
                  new Error('*** Time must be specified in seconds. (ex: 60s)'),
                );
              got.time = instructions ? util.format('%ds', time) : '';
            }

            if (headers.has('lanes')) {
              got.lanes = (lanes || '').trim();
            }

            if (headers.has('speed')) {
              if (row.speed !== '' && instructions) {
                if (!row.speed.match(/\d+ km\/h/))
                  return reject(
                    new Error(
                      '*** Speed must be specified in km/h. (ex: 50 km/h)',
                    ),
                  );
                const speed =
                  time > 0 ? Math.round((3.6 * distance) / time) : null;
                got.speed = util.format('%d km/h', speed);
              } else {
                got.speed = '';
              }
            }

            if (headers.has('intersections')) {
              got.intersections = (intersections || '').trim();
            }

            if (headers.has('locations')) {
              got.locations = (locations || '').trim();
            }
            if (headers.has('waypoints_count')) {
              if ('waypoints' in json) {
                got.waypoints_count = json.waypoints.length;
              } else {
                got.waypoints_count = 0;
              }
            }
            /*
                        if (headers.has('approaches')){
                            got.approaches = (approaches || '').trim();
                        }*/
            // if header matches 'a:*', parse out the values for *
            // and return in that header
            headers.forEach((k) => {
              const whitelist = [
                'duration',
                'distance',
                'datasources',
                'nodes',
                'weight',
                'speed',
              ];
              const metadata_whitelist = ['datasource_names'];
              if (k.match(/^a:/)) {
                const a_type = k.slice(2);
                if (whitelist.indexOf(a_type) == -1)
                  return reject(new Error(`Unrecognized annotation field: ${a_type}`));
                if (annotation && !annotation[a_type])
                  return reject(
                    new Error(`Annotation not found in response: ${a_type}`),
                  );
                got[k] = (annotation && annotation[a_type]) || '';
              } else if (k.match(/^am:/)) {
                const a_type = k.slice(3);
                if (metadata_whitelist.indexOf(a_type) == -1)
                  return reject(new Error(`Unrecognized annotation field: ${a_type}`));
                if (
                  annotation &&
                  (!annotation.metadata || !annotation.metadata[a_type])
                )
                  return reject(
                    new Error(`Annotation not found in response: ${a_type}`),
                  );
                got[k] =
                  (annotation &&
                    annotation.metadata &&
                    annotation.metadata[a_type]) ||
                  '';
              }
            });

            const putValue = function (key, value) {
              if (headers.has(key)) got[key] = instructions ? value : '';
            };

            putValue('ref', refs);
            putValue('bearing', bearings);
            putValue('turns', turns);
            putValue('modes', modes);
            putValue('classes', classes);
            putValue('times', times);
            putValue('distances', distances);
            putValue('pronunciations', pronunciations);
            putValue('destinations', destinations);
            putValue('exits', exits);
            putValue('weight_name', weight_name);
            putValue('weights', weights);
            putValue('weight', weight);
            putValue('approach', approaches);

            if (driving_sides) {
              putValue('driving_side', driving_sides);
            }

            for (const key in row) {
              if (this.FuzzyMatch.match(got[key], row[key])) {
                got[key] = row[key];
              }
            }
            resolve(got);
          } else {
            reject(new Error('request failed to return valid body'));
          }
        };

        if (headers.has('request')) {
          got = { request: row.request };
          this.requestUrl(row.request, afterRequest);
        } else {
          const defaultParams = this.queryParams;
          const userParams = [];
          got = {};
          for (const k in row) {
            const match = k.match(/param:(.*)/);
            if (match) {
              if (row[k] === '(nil)') {
                userParams.push([match[1], null]);
              } else if (row[k]) {
                userParams.push([match[1], row[k]]);
              }
              got[k] = row[k];
            }
          }

          const params = this.overwriteParams(defaultParams, userParams),
            waypoints = [];
          let bearings = [],
            approaches = [];

          if (row.bearings) {
            got.bearings = row.bearings;
            bearings = row.bearings.split(' ').filter((b) => !!b);
          }

          if (row.approaches) {
            got.approaches = row.approaches;
            approaches = row.approaches.split(' ').filter((b) => !!b);
          }

          if (row.from && row.to) {
            const fromNode = this.findNodeByName(row.from);
            if (!fromNode)
              return reject(
                new Error(util.format('*** unknown from-node "%s"', row.from)),
              );
            waypoints.push(fromNode);

            const toNode = this.findNodeByName(row.to);
            if (!toNode)
              return reject(
                new Error(util.format('*** unknown to-node "%s"', row.to)),
              );
            waypoints.push(toNode);

            got.from = row.from;
            got.to = row.to;
            this.requestRoute(
              waypoints,
              bearings,
              approaches,
              params,
              afterRequest,
            );
          } else if (row.waypoints) {
            row.waypoints.split(',').forEach((n) => {
              const node = this.findNodeByName(n.trim());
              if (!node)
                return reject(
                  new Error(
                    util.format('*** unknown waypoint node "%s"', n.trim()),
                  ),
                );
              waypoints.push(node);
            });
            got.waypoints = row.waypoints;
            this.requestRoute(
              waypoints,
              bearings,
              approaches,
              params,
              afterRequest,
            );
          } else {
            return reject(new Error('*** no waypoints'));
          }
        }
      });
    };

    await this.processRowsAndDiff(table, requestRow);
  }
}
