import Serializer from './http';
import { set } from '@ember/object';
import {
  HEADERS_SYMBOL as HTTP_HEADERS_SYMBOL,
  HEADERS_INDEX as HTTP_HEADERS_INDEX,
  HEADERS_DATACENTER as HTTP_HEADERS_DATACENTER,
  HEADERS_NAMESPACE as HTTP_HEADERS_NAMESPACE,
} from 'consul-ui/utils/http/consul';
import { CACHE_CONTROL as HTTP_HEADERS_CACHE_CONTROL } from 'consul-ui/utils/http/headers';
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
import { NSPACE_KEY } from 'consul-ui/models/nspace';
import createFingerprinter from 'consul-ui/utils/create-fingerprinter';

const DEFAULT_NSPACE = '';

const map = function(obj, cb) {
  if (!Array.isArray(obj)) {
    return [obj].map(cb)[0];
  }
  return obj.map(cb);
};

const attachHeaders = function(headers, body, query = {}) {
  // lowercase everything incase we get browser inconsistencies
  const lower = {};
  Object.keys(headers).forEach(function(key) {
    lower[key.toLowerCase()] = headers[key];
  });
  // Add a 'pretend' Datacenter/Nspace header, they are not headers the come
  // from the request but we add them here so we can use them later for store
  // reconciliation
  if (typeof query.dc !== 'undefined') {
    lower[HTTP_HEADERS_DATACENTER.toLowerCase()] = query.dc;
  }
  lower[HTTP_HEADERS_NAMESPACE.toLowerCase()] =
    typeof query.ns !== 'undefined' ? query.ns : DEFAULT_NSPACE;
  //
  body[HTTP_HEADERS_SYMBOL] = lower;
  return body;
};

export default class ApplicationSerializer extends Serializer {
  attachHeaders = attachHeaders;
  fingerprint = createFingerprinter(DATACENTER_KEY, NSPACE_KEY);

  respondForQuery(respond, query) {
    return respond((headers, body) =>
      attachHeaders(
        headers,
        map(body, this.fingerprint(this.primaryKey, this.slugKey, query.dc)),
        query
      )
    );
  }

  respondForQueryRecord(respond, query) {
    return respond((headers, body) =>
      attachHeaders(headers, this.fingerprint(this.primaryKey, this.slugKey, query.dc)(body), query)
    );
  }

  respondForCreateRecord(respond, serialized, data) {
    const slugKey = this.slugKey;
    const primaryKey = this.primaryKey;
    return respond((headers, body) => {
      // If creates are true use the info we already have
      if (body === true) {
        body = data;
      }
      // Creates need a primaryKey adding
      return this.fingerprint(primaryKey, slugKey, data[DATACENTER_KEY])(body);
    });
  }

  respondForUpdateRecord(respond, serialized, data) {
    const slugKey = this.slugKey;
    const primaryKey = this.primaryKey;
    return respond((headers, body) => {
      // If updates are true use the info we already have
      // TODO: We may aswell avoid re-fingerprinting here if we are just going
      // to reuse data then its already fingerprinted and as the response is
      // true we don't have anything changed so the old fingerprint stays the
      // same as long as nothing in the fingerprint has been edited (the
      // namespace?)
      if (body === true) {
        body = data;
      }
      return this.fingerprint(primaryKey, slugKey, data[DATACENTER_KEY])(body);
    });
  }

  respondForDeleteRecord(respond, serialized, data) {
    const slugKey = this.slugKey;
    const primaryKey = this.primaryKey;
    return respond((headers, body) => {
      // Deletes only need the primaryKey/uid returning and they need the slug
      // key AND potential namespace in order to create the correct
      // uid/fingerprint
      return {
        [primaryKey]: this.fingerprint(
          primaryKey,
          slugKey,
          data[DATACENTER_KEY]
        )({
          [slugKey]: data[slugKey],
          [NSPACE_KEY]: data[NSPACE_KEY],
        })[primaryKey],
      };
    });
  }

  // this could get confusing if you tried to override say
  // `normalizeQueryResponse`
  // TODO: consider creating a method for each one of the
  // `normalize...Response` family
  normalizeResponse(store, modelClass, payload, id, requestType) {
    const normalizedPayload = this.normalizePayload(payload, id, requestType);
    // put the meta onto the response, here this is ok as JSON-API allows this
    // and our specific data is now in response[primaryModelClass.modelName]
    // so we aren't in danger of overwriting anything (which was the reason
    // for the Symbol-like property earlier) use a method modelled on
    // ember-data methods so we have the opportunity to do this on a per-model
    // level
    const meta = this.normalizeMeta(store, modelClass, normalizedPayload, id, requestType);
    if (requestType !== 'query') {
      normalizedPayload.meta = meta;
    }
    const res = super.normalizeResponse(
      store,
      modelClass,
      {
        meta: meta,
        [modelClass.modelName]: normalizedPayload,
      },
      id,
      requestType
    );
    // If the result of the super normalizeResponse is undefined its because
    // the JSONSerializer (which REST inherits from) doesn't recognise the
    // requestType, in this case its likely to be an 'action' request rather
    // than a specific 'load me some data' one. Therefore its ok to bypass the
    // store here for the moment we currently use this for self, but it also
    // would affect any custom methods that use a serializer in our custom
    // service/store
    if (typeof res === 'undefined') {
      return payload;
    }
    return res;
  }

  timestamp() {
    return new Date().getTime();
  }

  normalizeMeta(store, modelClass, payload, id, requestType) {
    // Pick the meta/headers back off the payload and cleanup
    const headers = payload[HTTP_HEADERS_SYMBOL] || {};
    delete payload[HTTP_HEADERS_SYMBOL];
    const meta = {
      cacheControl: headers[HTTP_HEADERS_CACHE_CONTROL.toLowerCase()],
      cursor: headers[HTTP_HEADERS_INDEX.toLowerCase()],
      dc: headers[HTTP_HEADERS_DATACENTER.toLowerCase()],
      nspace: headers[HTTP_HEADERS_NAMESPACE.toLowerCase()],
    };
    if (typeof headers['x-range'] !== 'undefined') {
      meta.range = headers['x-range'];
    }
    if (typeof headers['refresh'] !== 'undefined') {
      meta.interval = headers['refresh'] * 1000;
    }
    if (requestType === 'query') {
      meta.date = this.timestamp();
      payload.forEach(function(item) {
        set(item, 'SyncTime', meta.date);
      });
    }
    return meta;
  }

  normalizePayload(payload, id, requestType) {
    return payload;
  }
}
