"use strict";

const VError = require("verror");
const extend = require("./util/extend");

module.exports = function (dependencies) {
  const config          = dependencies.config;
  const EndpointManager = dependencies.EndpointManager;

  function Client (options) {
    this.config = config(options);

    this.endpointManager = new EndpointManager(this.config);
    this.endpointManager.on("wakeup", () => {
      while (this.queue.length > 0) {
        const stream = this.endpointManager.getStream();
        if (!stream) {
          return;
        }
        const resolve = this.queue.shift();
        resolve(stream);
      }

      if (this.shutdownPending) {
        this.endpointManager.shutdown();
      }
    });

    this.endpointManager.on("error", (err) => {
      this.queue.forEach((resolve) => {
        resolve(Promise.reject(err));
      });

      this.queue = [];
    });

    this.queue = [];
  }

  Client.prototype.write = function write (notification, device, count) {
    return this.getStream().then( stream => {
      let tokenGeneration, status, responseData = "";
      let retryCount = count || 0;

      stream.setEncoding("utf8");

      stream.on("headers", headers => {
        status = headers[":status"];
      });

      stream.on("data", data => {
        responseData = responseData + data;
      });

      let headers = extend({
        ":scheme": "https",
        ":method": "POST",
        ":authority": this.config.address,
        ":path": "/3/device/" + device,
      }, notification.headers);

      if (this.config.token) {
        if (this.config.token.isExpired(3300)) {
          this.config.token.regenerate(this.config.token.generation);
        }
        headers.authorization = "bearer " + this.config.token.current;
        tokenGeneration = this.config.token.generation;
      }

      stream.headers(headers);
      stream.write(notification.body);

      return new Promise ( resolve => {
        stream.on("end", () => {
          if (status === "200") {
            resolve({ device });
          } else if (responseData !== "") {
            const response = JSON.parse(responseData);

            if (status === "403" && response.reason === "ExpiredProviderToken" && retryCount < 2) {
              this.config.token.regenerate(tokenGeneration);
              resolve(this.write(notification, device, retryCount + 1));
              return;
            } else if (status === "500" && response.reason === "InternalServerError") {
              stream.connection.close();
              let error = new VError("Error 500, stream ended unexpectedly");
              resolve({ device, error });
              return;
            }

            resolve({ device, status, response });
          } else {
            let error = new VError("stream ended unexpectedly");
            resolve({ device, error });
          }
        });

        stream.on("unprocessed", () => {
          resolve(this.write(notification, device));
        });

        stream.on("error", err => {
          let error;
          if (typeof err === "string") {
            error = new VError("apn write failed: %s", err);
          } else {
            error = new VError(err, "apn write failed");
          }
          resolve({ device, error });
        });

        stream.end();
      });
    }).catch( error => {
      return { device, error };
    });
  };

  Client.prototype.getStream = function getStream() {
    return new Promise( resolve => {
      const stream = this.endpointManager.getStream();
      if (!stream) {
        this.queue.push(resolve);
      } else {
        resolve(stream);
      }
    });
  };

  Client.prototype.shutdown = function shutdown() {
    if (this.queue.length > 0) {
      this.shutdownPending = true;
      return;
    }
    this.endpointManager.shutdown();
  };

  return Client;
}
