import conductorV6, { globalOverageMessage } from '../conductor/conductorV6.js';
import { SessionContext } from '../conductor/types.js';
import { handleDebugMessage } from '../server-utils/debug.js';
import query, { transaction } from '../server-utils/query.js';
import { declareHandler } from '../server-utils/routesHandler.js';
import { SocketEvent } from '../server-utils/socketMessageHandler.js';
import { PlanGatedFeature, UserRole } from '../types/serverTypes.js';
import semanticSearch from '../utils/semanticSearch.js';
import trackServerEvent, { ActivityType } from '../utils/trackServerEvent.js';
import { getUserById } from './users.js';

/**
 * This file contains helper functions and route handlers for the
 * conductor service.
 *
 * Conductor is the chatbot service that uses OpenAI's ChatGPT API
 */

const BASIC_USER_CALL_LIMIT_PER_8_HOURS = 20;

const getConductorAccessStatus = async (userId) => {
  const requestCountResult = await query(
    `SELECT COUNT(*) FROM conductor_requests WHERE user_id = $1 AND (response->>'error')::boolean = false AND created_at > NOW() - INTERVAL '8 hours'`,
    [userId]
  );

  return {
    requestCount: parseInt(requestCountResult.rows[0].count),
  };
};

export const getConductorLimits = declareHandler({
  func: async (req, res) => {
    if (!req.user) {
      res.status(401);
      res.send({ status: 'error', message: 'Not authorized' });
      return;
    }

    const { requestCount } = await getConductorAccessStatus(req.user.id);

    res.status(200);
    res.send({ requestCount, requestLimitAmount: null, userId: req.user.id });
  },
});

export const conductorMessageHandler = async (socket, message) => {
  const user = socket.request.user;
  const request = message.lines[message.lines.length - 1]?.message || '';

  const { role, email } = await getUserById(user.id);

  if (request.startsWith('echo:') && email.endsWith('@wavtool.com')) {
    socket.emit(SocketEvent.Final, { error: false, message: request.replace('echo:', '').trim() });
    return;
  }

  if (request.length > 2500) {
    socket.emit(SocketEvent.Final, { error: true, message: 'Message too long' });
    return;
  }

  if (request.startsWith('debug:') && role === UserRole.Admin) {
    const returnResponse = await handleDebugMessage(message);
    return socket.emit(SocketEvent.Final, returnResponse);
  }

  if (process.env.OFFLINE_FOR_MAINTENANCE) {
    const globalOverageMessage = `WavTool is offline for some quick upgrades. Please try again in a few minutes!`;
    setTimeout(() => socket.emit(SocketEvent.Final, { error: true, message: globalOverageMessage }), 1500);
    return;
  }

  const { requestCount } = await getConductorAccessStatus(user.id);

  let userCallLimitPer8Hours = 250;
  let overageMessage = `Error: could not process request. Try again soon.`;

  if (role === UserRole.Admin) {
    userCallLimitPer8Hours = 1000000;
    overageMessage = `You've exceeded the admin conductor API call limit.\n...How did you even do that?`;
  }

  // Note: Indie plan still affected by global rate limit.
  if (role !== UserRole.Admin && !user.features[PlanGatedFeature.UnlimitedConductor]) {
    const globalRequestsLastHour = await query(
      `SELECT COUNT(*) FROM conductor_requests WHERE created_at > NOW() - INTERVAL '1 hour'`,
      []
    );

    const numGlobalRequestsLastHour = globalRequestsLastHour.rows[0].count;
    const segmentNumber = Number(user.id) + Math.floor(Date.now() / (1000 * 60 * 10));

    let preventedByGlobalRateLimit = false;

    // TODO: Figure out if these numbers still make sense.
    if (numGlobalRequestsLastHour > 550) {
      preventedByGlobalRateLimit = true;
    } else if (numGlobalRequestsLastHour > 350) {
      if (segmentNumber % 2 === 0) {
        preventedByGlobalRateLimit = true;
      }
    } else if (numGlobalRequestsLastHour > 300) {
      if (segmentNumber % 3 === 0) {
        preventedByGlobalRateLimit = true;
      }
    } else if (numGlobalRequestsLastHour > 250) {
      if (segmentNumber % 4 === 0) {
        preventedByGlobalRateLimit = true;
      }
    }

    if (preventedByGlobalRateLimit) {
      setTimeout(() => socket.emit(SocketEvent.Final, { error: true, message: globalOverageMessage }), 1500);
      return;
    }
  }

  const callLimitOverrideResult = await query(`SELECT message_limit_override FROM users WHERE id = $1`, [user.id]);
  if (callLimitOverrideResult.rows.length > 0 && callLimitOverrideResult.rows[0].message_limit_override !== null) {
    userCallLimitPer8Hours = callLimitOverrideResult.rows[0].message_limit_override;
  }

  if (requestCount >= userCallLimitPer8Hours) {
    trackServerEvent(user?.id, {
      name: ActivityType.ConductorOverage,
      properties: {
        requestCount,
        userCallLimitPer8Hours,
      },
    });
    setTimeout(() => socket.emit(SocketEvent.Final, { error: true, message: overageMessage }), 500);
    return;
  }

  let configTokenLimit = 40000;
  const configTokenLimitResult = await query(
    `SELECT value FROM config_vals WHERE name = 'conductor-token-quota-free-users'`,
    []
  );
  if (configTokenLimitResult.rows.length > 0) {
    const configTokenLimitResultAsNumber = Number(configTokenLimitResult.rows[0].value);
    if (!Number.isNaN(configTokenLimitResultAsNumber)) {
      configTokenLimit = configTokenLimitResultAsNumber;
    }
  }

  const tokensUsedThisMinuteResult = await query(
    `SELECT tokens_used FROM conductor_token_quota WHERE minute = floor(extract(epoch from now()) / 60)::integer`,
    []
  );
  const tokensUsedThisMinute =
    tokensUsedThisMinuteResult.rows.length > 0 ? tokensUsedThisMinuteResult.rows[0].tokens_used : 0;

  const response = await conductorV6(
    message.lines,
    message.tools,
    user,
    user.features.UnlimitedConductor ? null : configTokenLimit - tokensUsedThisMinute,
    message.sessionContext as SessionContext | null,
    (fragment) => {
      socket.emit(SocketEvent.Stream, fragment);
    },
    (cancel) => {
      if (!socket.connected) {
        cancel();
      }
      socket.on('disconnect', () => cancel());
    }
  );

  socket.emit(SocketEvent.Final, response);
};

export const getConductorStatusMessage = declareHandler({
  func: async (req, res) => {
    await transaction(async (query) => {
      const messageResult = await query(`SELECT value FROM config_vals WHERE name = 'conductor-status-message'`, []);

      // severity should be 'info', 'warning', or 'error'
      const severityResult = await query(`SELECT value FROM config_vals WHERE name = 'conductor-status-severity'`, []);

      if (messageResult.rows.length === 0) {
        res.send({});
      } else {
        res.send({
          message: messageResult.rows[0].value,
          severity: severityResult.rows[0]?.value || 'info',
        });
      }
    });
  },
});

export const getSemanticRank = declareHandler({
  func: async (req, res) => {
    const options: string[] = req.body.options;
    const query: string = req.body.query;
    const ranked = await semanticSearch(query, options);
    res.send(ranked);
  },
});
