const { io } = require('socket.io-client');
const cron = require('node-cron');
const foodsharingClient = require('./foodsharingClient');
const requestLogStore = require('./requestLogStore');
const sessionStore = require('./sessionStore');

const FOODSHARING_URL = 'https://foodsharing.de';
const DEFAULT_FALLBACK_CRON = '0 6-23 * * *';
const STORE_DEBOUNCE_MS = 2 * 60 * 1000;

function extractStoreIdFromHref(href = '') {
  if (typeof href !== 'string' || !href) {
    return null;
  }

  const directMatch = href.match(/\/store\/(\d+)/);
  if (directMatch) {
    return directMatch[1];
  }

  const queryMatch = href.match(/[?&]id=(\d+)/);
  if (queryMatch && href.includes('fsbetrieb')) {
    return queryMatch[1];
  }

  return null;
}

function getMatchingActiveEntries(config, storeId) {
  if (!Array.isArray(config) || !storeId) {
    return [];
  }
  return config.filter((entry) => entry?.active && String(entry.id) === String(storeId));
}

function summarizePayload(payload, depth = 0) {
  if (payload === null || payload === undefined) {
    return payload;
  }
  if (typeof payload !== 'object') {
    return typeof payload === 'string' && payload.length > 240 ? `${payload.slice(0, 240)}…` : payload;
  }
  if (depth >= 2) {
    return Array.isArray(payload) ? `[array:${payload.length}]` : '[object]';
  }
  if (Array.isArray(payload)) {
    return payload.slice(0, 5).map((entry) => summarizePayload(entry, depth + 1));
  }
  return Object.fromEntries(
    Object.entries(payload)
      .slice(0, 20)
      .map(([key, value]) => [key, summarizePayload(value, depth + 1)])
  );
}

function sortConversationsAscending(conversations = []) {
  return conversations.slice().sort((a, b) => {
    const left = new Date(a?.lastMessage?.sentAt || 0).getTime() || 0;
    const right = new Date(b?.lastMessage?.sentAt || 0).getTime() || 0;
    if (left !== right) {
      return left - right;
    }
    return Number(a?.lastMessage?.id || 0) - Number(b?.lastMessage?.id || 0);
  });
}

function collectStoreIds(value, ids = new Set(), depth = 0) {
  if (value === null || value === undefined || depth > 5) {
    return ids;
  }
  if (typeof value === 'string') {
    const hrefId = extractStoreIdFromHref(value);
    if (hrefId) {
      ids.add(hrefId);
    }
    return ids;
  }
  if (typeof value !== 'object') {
    return ids;
  }
  if (Array.isArray(value)) {
    value.forEach((entry) => collectStoreIds(entry, ids, depth + 1));
    return ids;
  }

  const directStoreId = value.storeId ?? value.store_id ?? value.store?.id ?? value.store?.storeId;
  if (directStoreId) {
    ids.add(String(directStoreId));
  }
  if (value.href || value.url || value.link) {
    collectStoreIds(value.href || value.url || value.link, ids, depth + 1);
  }
  Object.values(value).forEach((entry) => collectStoreIds(entry, ids, depth + 1));
  return ids;
}

function getMessageWatcherSettings(settings = {}) {
  const fallbackCron = settings.messageWatcherFallbackCron || DEFAULT_FALLBACK_CRON;
  return {
    socketEnabled: settings.messageWatcherSocketEnabled !== false,
    fallbackEnabled: settings.messageWatcherFallbackEnabled !== false,
    fallbackCron: cron.validate(fallbackCron) ? fallbackCron : DEFAULT_FALLBACK_CRON,
    debugLogging: settings.messageWatcherDebugLogging !== false
  };
}

function isPotentialMessageSocketEvent(eventName = '') {
  return /message|conversation|bell|notification|wall|post/i.test(String(eventName));
}

function startStoreMessageWatcher({ sessionId, config, settings, runPickupCheck }) {
  const watcherSettings = getMessageWatcherSettings(settings);
  let stopped = false;
  let fallbackJob = null;
  let socket = null;
  let socketConnected = false;
  let conversationsBaselineReady = false;
  const lastMessageByConversation = new Map();
  const lastTriggerByStore = new Map();
  const inFlightStores = new Set();

  function logWatcherEvent(type, data = {}, options = {}) {
    const session = sessionStore.get(sessionId);
    const payload = {
      event: type,
      ...data
    };
    const consoleMessage = `[MESSAGES] ${type}: ${JSON.stringify(payload)}`;
    const level = options.level || 'info';
    if (level === 'warn') {
      console.warn(consoleMessage);
    } else if (level === 'error') {
      console.error(consoleMessage);
    } else {
      console.log(consoleMessage);
    }

    if (!watcherSettings.debugLogging && !options.forceLog) {
      return;
    }
    try {
      requestLogStore.add({
        direction: 'incoming',
        target: 'foodsharing.de',
        method: 'MESSAGE_WATCHER',
        path: type,
        status: null,
        sessionId,
        profileId: session?.profile?.id ?? null,
        profileName: session?.profile?.name ?? null,
        responseBody: payload
      });
    } catch (error) {
      console.warn('[MESSAGES] Watcher-Log konnte nicht geschrieben werden:', error.message);
    }
  }

  async function triggerStoreCheck(storeId, source = {}) {
    const matchingEntries = getMatchingActiveEntries(config, storeId);
    if (matchingEntries.length === 0) {
      logWatcherEvent('trigger-skipped-no-active-entry', { storeId, source: source.type });
      return;
    }

    const now = Date.now();
    const lastTrigger = lastTriggerByStore.get(storeId) || 0;
    if (now - lastTrigger < STORE_DEBOUNCE_MS || inFlightStores.has(storeId)) {
      logWatcherEvent('trigger-skipped-debounce', { storeId, source: source.type });
      return;
    }

    lastTriggerByStore.set(storeId, now);
    inFlightStores.add(storeId);
    const storeName = source.storeName || matchingEntries[0]?.name || `Store ${storeId}`;
    logWatcherEvent(
      'store-message-pickup-check',
      {
        storeId,
        storeName,
        checkedEntries: matchingEntries.length,
        source
      },
      { forceLog: true }
    );

    try {
      await runPickupCheck(sessionId, matchingEntries, settings);
      logWatcherEvent('store-message-pickup-check-complete', {
        storeId,
        checkedEntries: matchingEntries.length,
        source: source.type
      });
    } catch (error) {
      logWatcherEvent(
        'store-message-pickup-check-error',
        { storeId, source: source.type, error: error.message },
        { level: 'error', forceLog: true }
      );
    } finally {
      inFlightStores.delete(storeId);
    }
  }

  async function processSocketEvent(eventName, args = []) {
    const summarizedArgs = summarizePayload(args);
    const storeIds = Array.from(collectStoreIds(args));
    logWatcherEvent('socket-event', {
      eventName,
      storeIds,
      payload: summarizedArgs
    });

    if (!isPotentialMessageSocketEvent(eventName)) {
      return;
    }

    for (const storeId of storeIds) {
      await triggerStoreCheck(storeId, {
        type: 'socket',
        eventName
      });
    }
  }

  async function pollConversations(reason) {
    const session = sessionStore.get(sessionId);
    if (!session?.cookieHeader) {
      return;
    }

    const { conversations } = await foodsharingClient.fetchConversations(
      session.cookieHeader,
      { limit: 20, offset: 0 },
      session
    );
    const sorted = sortConversationsAscending(conversations);
    let newMessages = 0;

    if (!conversationsBaselineReady) {
      sorted.forEach((conversation) => {
        if (conversation?.id && conversation?.lastMessage?.id) {
          lastMessageByConversation.set(String(conversation.id), String(conversation.lastMessage.id));
        }
      });
      conversationsBaselineReady = true;
      logWatcherEvent('fallback-conversations-baseline', {
        reason,
        conversations: sorted.length
      });
      return;
    }

    for (const conversation of sorted) {
      const conversationId = conversation?.id ? String(conversation.id) : null;
      const lastMessageId = conversation?.lastMessage?.id ? String(conversation.lastMessage.id) : null;
      if (!conversationId || !lastMessageId) {
        continue;
      }
      const previousMessageId = lastMessageByConversation.get(conversationId);
      lastMessageByConversation.set(conversationId, lastMessageId);
      if (!previousMessageId || previousMessageId === lastMessageId) {
        continue;
      }
      newMessages += 1;
      if (!conversation.storeId) {
        logWatcherEvent('conversation-message-without-store', {
          conversationId,
          lastMessageId
        });
        continue;
      }
      await triggerStoreCheck(String(conversation.storeId), {
        type: 'conversation-fallback',
        conversationId,
        messageId: lastMessageId,
        storeName: conversation.title || null,
        sentAt: conversation.lastMessage?.sentAt || null,
        body: summarizePayload(conversation.lastMessage?.body || '')
      });
    }

    logWatcherEvent('fallback-conversations-polled', {
      reason,
      conversations: sorted.length,
      newMessages
    });
  }

  async function runFallbackPoll(reason) {
    if (stopped || !watcherSettings.fallbackEnabled) {
      return;
    }
    try {
      await pollConversations(reason);
    } catch (error) {
      logWatcherEvent(
        'fallback-poll-error',
        { reason, error: error.message },
        { level: 'warn', forceLog: true }
      );
    }
  }

  function scheduleFallbackPoll() {
    if (stopped || !watcherSettings.fallbackEnabled) {
      return;
    }
    fallbackJob = cron.schedule(watcherSettings.fallbackCron, async () => {
      if (!socketConnected) {
        await runFallbackPoll('socket-disconnected');
      } else {
        logWatcherEvent('fallback-poll-skipped-socket-connected', {
          cron: watcherSettings.fallbackCron
        });
      }
    });
  }

  function startSocket() {
    if (!watcherSettings.socketEnabled) {
      logWatcherEvent('socket-disabled');
      return;
    }
    const session = sessionStore.get(sessionId);
    if (!session?.cookieHeader) {
      logWatcherEvent('socket-skipped-missing-cookie', {}, { level: 'warn', forceLog: true });
      return;
    }

    socket = io(FOODSHARING_URL, {
      path: '/websocket/socket.io',
      transports: ['websocket', 'polling'],
      extraHeaders: {
        Cookie: session.cookieHeader,
        Origin: FOODSHARING_URL,
        Referer: `${FOODSHARING_URL}/dashboard`
      },
      reconnection: true,
      reconnectionDelay: 10000,
      reconnectionDelayMax: 60000,
      timeout: 20000,
      forceNew: true
    });

    socket.on('connect', () => {
      socketConnected = true;
      logWatcherEvent('socket-connected', {
        socketId: socket.id,
        transport: socket.io?.engine?.transport?.name || null
      }, { forceLog: true });
    });

    socket.on('disconnect', (reason) => {
      socketConnected = false;
      logWatcherEvent('socket-disconnected', { reason }, { level: 'warn', forceLog: true });
    });

    socket.on('connect_error', (error) => {
      socketConnected = false;
      logWatcherEvent(
        'socket-connect-error',
        { error: error.message },
        { level: 'warn', forceLog: true }
      );
    });

    socket.io.on('reconnect_attempt', (attempt) => {
      logWatcherEvent('socket-reconnect-attempt', { attempt });
    });

    socket.io.on('upgrade', (transport) => {
      logWatcherEvent('socket-transport-upgrade', {
        transport: transport?.name || null
      });
    });

    socket.onAny((eventName, ...args) => {
      processSocketEvent(eventName, args).catch((error) => {
        logWatcherEvent(
          'socket-event-error',
          { eventName, error: error.message },
          { level: 'error', forceLog: true }
        );
      });
    });
  }

  logWatcherEvent('watcher-started', {
    socketEnabled: watcherSettings.socketEnabled,
    fallbackEnabled: watcherSettings.fallbackEnabled,
    fallbackCron: watcherSettings.fallbackCron,
    debugLogging: watcherSettings.debugLogging,
    activeEntries: Array.isArray(config) ? config.filter((entry) => entry?.active).length : 0
  }, { forceLog: true });
  startSocket();
  if (watcherSettings.fallbackEnabled) {
    runFallbackPoll('initial-baseline').finally(scheduleFallbackPoll);
  }

  return {
    stop() {
      stopped = true;
      if (fallbackJob) {
        fallbackJob.stop();
      }
      if (socket) {
        socket.disconnect();
      }
      logWatcherEvent('watcher-stopped', {}, { forceLog: true });
    }
  };
}

module.exports = {
  collectStoreIds,
  extractStoreIdFromHref,
  getMatchingActiveEntries,
  getMessageWatcherSettings,
  isPotentialMessageSocketEvent,
  startStoreMessageWatcher
};
