/* global window, document */
window.Niwa = window.Niwa || {};

window.Niwa.AdminChatPoller = (function () {
  'use strict';

  function create(opts) {
    opts = opts || {};
    var store = opts.store || null;
    var history = opts.history || null;
    var status = opts.status || null;
    var onBotTail = typeof opts.onBotTail === 'function' ? opts.onBotTail : function () {};
    var idleMs = resolveAdminHistoryPollMs();
    var limit = parseInt(opts.limit || 30, 10) || 30;
    var tailTimer = null;
    var idleTimer = null;
    var tailInFlight = false;
    var tailQueued = false;
    var tailBackoffMs = 2000;

    function pollTail() {
      if (!store || !history || document.hidden) return Promise.resolve();
      var state = store.getState();
      var jobs = store.getPendingJobs ? store.getPendingJobs() : (state.pending ? [state.pending] : []);
      if (!state.isOpen && !jobs.length) return Promise.resolve();
      if (tailInFlight) {
        tailQueued = true;
        return Promise.resolve();
      }

      tailInFlight = true;
      stopTailPoll();
      var sinceTs = state.newestTs || (jobs.length ? Math.max(0, Math.floor(jobs[0].sentAt / 1000) - 5) : 0);
      var sinceId = state.newestId || '';
	  var params = { includeHistory: true, sinceId: sinceId, sinceTs: sinceTs };
      var request;
	  if (jobs.length && status && typeof status.requestStatuses === 'function') {
		request = status.requestStatuses(jobs, params);
	  } else if (jobs.length && status && typeof status.requestStatus === 'function') {
		request = status.requestStatus(jobs[0].jobId || '', Object.assign({ requestId: jobs[0].requestId || '' }, params)).then(function (single) {
		  single = single || {};
		  return { jobs: [single], history: single.history || [] };
		});
	  } else {
		request = history.requestHistory({ mode: 'tail', limit: limit, since_id: sinceId, since_ts: sinceTs });
	  }

      return request.then(function (payload) {
        payload = payload || {};
        store.appendMany(payload.history || []);
        reconcileJobs(payload.jobs || [], store.getState().messages);
        tailBackoffMs = 2000;
        if (store.getPendingJobs && store.getPendingJobs().length) scheduleTailPoll(tailBackoffMs);
      }).catch(function () {
        if (store.getPendingJobs && store.getPendingJobs().length) {
          tailBackoffMs = Math.min(tailBackoffMs * 2, 30000);
          scheduleTailPoll(tailBackoffMs);
        }
      }).then(function () {
        tailInFlight = false;
        if (tailQueued) {
          tailQueued = false;
          scheduleTailPoll(0);
        }
      });
    }

    function reconcileJobs(statuses, messages) {
	  var currentState = store.getState();
      var pending = store.getPendingJobs ? store.getPendingJobs() : (currentState.pending ? [currentState.pending] : []);
      var byIdentity = {};
      for (var i = 0; i < statuses.length; i++) {
        if (statuses[i].jobId) byIdentity[statuses[i].jobId] = statuses[i];
        if (statuses[i].requestId) byIdentity[statuses[i].requestId] = statuses[i];
      }
      for (var j = 0; j < pending.length; j++) {
        var job = pending[j];
        var result = byIdentity[job.jobId] || byIdentity[job.requestId] || null;
		if (result && job.generation && result.generation && result.generation < job.generation) continue;
        if (hasCorrelatedBotReply(messages, job)) {
          store.clearPending(job.jobId || job.requestId);
          onBotTail(job);
          continue;
        }
        if (result && (result.status === 'pending' || result.status === 'processing')) {
          store.setPending(result.message || job.text, job.sentAt, {
            jobId: result.jobId || job.jobId,
            requestId: result.requestId || job.requestId,
            session: result.session || job.session,
            generation: result.generation || job.generation,
            sequence: result.sequence || job.sequence,
            phase: result.phase || result.status
          });
          continue;
        }
        if (result && result.status === 'failed') {
          store.clearPending(job.jobId || job.requestId);
          if (result.error) appendTerminalError(job, result);
          onBotTail(job);
          continue;
        }
        if (result && (result.status === 'missing' || result.status === 'idle') && Date.now() - job.sentAt > 20 * 60 * 1000) {
          store.clearPending(job.jobId || job.requestId);
          appendTerminalError(job, { error: 'Admin chat recovery expired. Send the message again.' });
          onBotTail(job);
        }
      }
    }

    function appendTerminalError(job, payload) {
      var identity = payload.jobId || job.jobId || payload.requestId || job.requestId || '';
      store.append({
        id: identity ? 'admin_error_' + identity : undefined,
        role: 'bot',
        text: payload.error,
        ts: Math.floor(Date.now() / 1000),
        source: 'admin_error',
        requestId: payload.requestId || job.requestId || '',
        jobId: payload.jobId || job.jobId || '',
        local: true
      });
    }

    function hasCorrelatedBotReply(messages, job) {
      for (var i = messages.length - 1; i >= 0; i--) {
        if (messages[i].role !== 'bot') continue;
        if (job.requestId && messages[i].requestId === job.requestId) return true;
        if (job.jobId && messages[i].jobId === job.jobId) return true;
      }
      return false;
    }

    function scheduleTailPoll(delay) {
      stopTailPoll();
      tailTimer = window.setTimeout(pollTail, Math.max(0, delay || 0));
    }

    function stopTailPoll() {
      if (tailTimer) window.clearTimeout(tailTimer);
      tailTimer = null;
    }

    function startIdleTailPoll() {
      if (!store || document.hidden || !store.getState().isOpen || idleTimer) return;
      scheduleIdleTick();
    }

    function scheduleIdleTick() {
      if (idleTimer || !store || document.hidden || !store.getState().isOpen) return;
      idleTimer = window.setTimeout(function () {
        idleTimer = null;
        if (!store || document.hidden || !store.getState().isOpen) return;
        pollTail();
        scheduleIdleTick();
      }, jitterDelay(idleMs));
    }

    function stopIdleTailPoll() {
      if (idleTimer) window.clearTimeout(idleTimer);
      idleTimer = null;
    }

    return {
      pollTail: pollTail,
      scheduleTailPoll: scheduleTailPoll,
      stopTailPoll: stopTailPoll,
      startIdleTailPoll: startIdleTailPoll,
      stopIdleTailPoll: stopIdleTailPoll
    };
  }

  function resolveAdminHistoryPollMs() {
    var poll = window.NiwaAI && window.NiwaAI.poll ? window.NiwaAI.poll : {};
    var configured = parseInt(poll.admin_history_poll_ms || 0, 10);
    if (!configured || configured < 30000) return 60000;
    return Math.min(configured, 300000);
  }

  function jitterDelay(baseMs) {
    var base = Math.max(30000, parseInt(baseMs || 60000, 10) || 60000);
    var spread = Math.round(base * 0.2);
    return Math.max(30000, base - spread + Math.floor(Math.random() * ((spread * 2) + 1)));
  }

  return { create: create };
})();
