const { db } = require("./db");
const { incrementCommands } = require("./stats");
const {
  buildCommandContext,
  runAdvancedCommand,
  normalizeCommandResult
} = require("./commands");
const { normalizeRandomReplies, selectRandomReply } = require("./command-random");
const { findConditionalReply, normalizeConditionalReplies } = require("./command-conditional");
const { beginCommandUse, commandKey, customCommandKey } = require("./command-policies");
const { getEnabledPlatformIds, normalizePlatformSelection } = require("./platforms");
const placeholders = require("./placeholders");
const { createLogger } = require("./logger");

const commandLog = createLogger("core:commands", { category: "command" });

function createCommandRouter({ settings }) {
  const commandMap = new Map();
  const pluginCommands = new Map();
  const registry = new Map();

  function clearCommands(pluginId) {
    const existing = pluginCommands.get(pluginId) || [];
    for (const entry of existing) {
      const handlers = commandMap.get(entry.trigger) || [];
      const nextHandlers = handlers.filter((handler) => handler !== entry.handler);
      if (nextHandlers.length) {
        commandMap.set(entry.trigger, nextHandlers);
      } else {
        commandMap.delete(entry.trigger);
      }
    }
    pluginCommands.delete(pluginId);
    registry.delete(pluginId);
  }

  function registerCommands(pluginId, commands = []) {
    if (!pluginId) {
      throw new Error("Plugin id is required to register commands.");
    }
    clearCommands(pluginId);
    const entries = [];
    const registered = [];
    for (const command of commands) {
      const triggers = (command.triggers || [])
        .map((trigger) => trigger.toLowerCase())
        .filter(Boolean);
      const handler = buildHandler(pluginId, command, triggers);
      for (const trigger of triggers) {
        const list = commandMap.get(trigger) || [];
        list.push(handler);
        commandMap.set(trigger, list);
        entries.push({ trigger, handler });
      }
      registered.push({
        key: handler.commandKey,
        pluginId,
        id: handler.commandId,
        triggers,
        platforms: handler.platforms,
        description: String(command.description || ""),
        defaultPolicy: handler.defaultPolicy
      });
    }
    pluginCommands.set(pluginId, entries);
    registry.set(pluginId, registered);
  }

  function buildHandler(pluginId, command, triggers) {
    const handler = async (ctx) => {
      return await command.handler(ctx);
    };
    handler.commandId = command.id || triggers[0] || null;
    handler.commandKey = commandKey(pluginId, handler.commandId);
    handler.platforms = Array.isArray(command.platforms) ? command.platforms : [];
    handler.defaultPolicy = command.defaultPolicy || {};
    return handler;
  }

  function listCommands() {
    return Array.from(registry.values()).flat().map((entry) => ({ ...entry, triggers: [...entry.triggers], platforms: [...entry.platforms] }));
  }

  async function handleMessage({ platform, raw, user, platformUser, reply, meta }) {
    const prefix = settings.getSetting("command_prefix", "!");
    if (!raw.startsWith(prefix)) {
      return false;
    }
    const rawCommand = raw.slice(prefix.length).trim();
    if (!rawCommand) {
      return false;
    }
    const parts = rawCommand.split(/\s+/);
    const trigger = parts[0].toLowerCase();
    const args = parts.slice(1);
    const argsText = args.join(" ");
    const ctx = {
      platform,
      trigger,
      raw,
      args,
      argsText,
      user: {
        id: user.id,
        username: user.internal_username || user.username,
        platformId: platformUser.id,
        displayName: platformUser.displayName || platformUser.username,
        tag: platformUser.tag
      },
      platformUser,
      meta,
      reply
    };

    const customHandled = await handleCustomCommand({
      trigger,
      platform,
      ctx,
      raw,
      reply
    });
    if (customHandled) {
      incrementCommands(user.id);
      commandLog.debug("Custom command completed", {
        trigger,
        platform,
        user_id: user.id
      }, { event: "custom_command_completed" });
      return true;
    }

    const handlers = commandMap.get(trigger) || [];
    for (const handler of handlers) {
      if (handler.platforms.length && !handler.platforms.includes(ctx.platform)) continue;
      let lease;
      try {
        lease = await beginCommandUse({
          commandKey: handler.commandKey,
          ctx,
          defaultPolicy: handler.defaultPolicy
        });
      } catch (error) {
        commandLog.error("Command policy check failed", { command_id: handler.commandId, trigger, platform, user_id: user.id, error }, { event: "command_policy_failed" });
        await safeReply(reply, "This command's access settings could not be checked.");
        return true;
      }
      if (!lease.allowed) {
        await safeReply(reply, lease.message);
        return true;
      }
      try {
        const result = await handler(ctx);
        if (typeof result === "string" && result) {
          await safeReply(reply, result);
          await lease.commit();
          recordCommandUsage(handler.commandId);
          incrementCommands(user.id);
          commandLog.debug("Command completed", {
            command_id: handler.commandId,
            trigger,
            platform,
            user_id: user.id
          }, { event: "command_completed" });
          return true;
        }
        if (result === true) {
          await lease.commit();
          recordCommandUsage(handler.commandId);
          incrementCommands(user.id);
          commandLog.debug("Command completed", {
            command_id: handler.commandId,
            trigger,
            platform,
            user_id: user.id
          }, { event: "command_completed" });
          return true;
        }
        await lease.rollback("Command handler did not accept the invocation.");
      } catch (error) {
        await lease.rollback(error?.message || "Command handler failed.");
        commandLog.error("Command handler failed", {
          command_id: handler.commandId,
          trigger,
          platform,
          user_id: user.id,
          error
        }, { event: "command_failed" });
        await safeReply(reply, "Command failed to execute.");
        return true;
      }
    }

    return false;
  }

  return {
    registerCommands,
    clearCommands,
    handleMessage,
    listCommands
  };
}

async function handleCustomCommand({ trigger, platform, ctx, raw, reply }) {
  const row = db
    .prepare(
      "SELECT id, response, mode, language, code, platform, random_replies_json, rng_enabled, rng_min, rng_max, conditional_replies_json, conditional_fuzzy FROM custom_commands WHERE trigger = ? AND enabled = 1"
    )
    .get(trigger);
  if (!row) {
    return false;
  }
  const enabledPlatforms = getEnabledPlatformIds();
  const allowedPlatforms = normalizePlatformSelection(row.platform, enabledPlatforms);
  if (!allowedPlatforms.includes(platform)) {
    return false;
  }
  let lease;
  try {
    lease = await beginCommandUse({ commandKey: customCommandKey(row.id), ctx });
  } catch (error) {
    commandLog.error("Custom command policy check failed", { trigger, platform, user_id: ctx.user.id, error }, { event: "command_policy_failed" });
    await safeReply(reply, "This command's access settings could not be checked.");
    return true;
  }
  if (!lease.allowed) {
    await safeReply(reply, lease.message);
    return true;
  }
  try {
    if (row.mode === "advanced" && row.code) {
      const messageInfo = buildMessageInfo(ctx, raw);
      const commandCtx = buildCommandContext({
        platform,
        user: {
          id: ctx.user.id,
          platformId: ctx.user.platformId,
          username: ctx.user.username,
          displayName: ctx.user.displayName,
          tag: ctx.user.tag
        },
        message: messageInfo,
        args: ctx.args,
        argsText: ctx.argsText
      });
      const result = await runAdvancedCommand(
        { code: row.code, language: row.language },
        commandCtx
      );
      const output = normalizeCommandResult(result);
      if (output) {
        await safeReply(reply, output);
      } else {
        await safeReply(reply, "Command ran but returned no output.");
      }
    } else if (row.mode === "random") {
      const selected = selectRandomReply({
        replies: normalizeRandomReplies(row.random_replies_json),
        rngEnabled: Boolean(row.rng_enabled),
        rngMin: row.rng_min,
        rngMax: row.rng_max
      });
      const rendered = await placeholders.renderTemplate({
        fieldId: "core.custom_commands.static_response",
        template: selected.reply.text,
        outputAudience: "user",
        user: {
          id: ctx.user.id,
          username: ctx.user.username,
          isAdmin: false,
          isMod: false
        },
        runtimeContext: { ctx, user: ctx.user, platform, runtime: true, command: { rng: selected.rng } }
      });
      await safeReply(reply, rendered.rendered);
    } else if (row.mode === "conditional") {
      const replies = normalizeConditionalReplies(row.conditional_replies_json);
      const found = findConditionalReply(replies, ctx.argsText, { fuzzy: Boolean(row.conditional_fuzzy) });
      let template = found.match?.response || "";
      if (!ctx.argsText) {
        template = row.response || `Available options: ${replies.map((entry) => entry.keyword).join(", ")}`;
      } else if (!found.match) {
        template = `Unknown option. Available options: ${replies.map((entry) => entry.keyword).join(", ")}`;
      }
      const rendered = await renderStaticResponse(template, ctx, platform, {
        conditional: { requested: ctx.argsText, keyword: found.match?.keyword || null, fuzzy: found.fuzzy }
      });
      await safeReply(reply, rendered);
    } else {
      await safeReply(reply, await renderStaticResponse(row.response, ctx, platform));
    }
    recordCommandUsage(`custom:${trigger}`);
    await lease.commit();
    return true;
  } catch (error) {
    await lease.rollback(error?.message || "Custom command failed.");
    commandLog.error("Custom command failed", {
      trigger,
      platform,
      user_id: ctx.user.id,
      error
    }, { event: "custom_command_failed" });
    await safeReply(reply, "Command failed to execute.");
    return true;
  }
}

async function renderStaticResponse(template, ctx, platform, extraRuntime = {}) {
  const rendered = await placeholders.renderTemplate({
    fieldId: "core.custom_commands.static_response",
    template,
    outputAudience: "user",
    user: {
      id: ctx.user.id,
      username: ctx.user.username,
      isAdmin: false,
      isMod: false
    },
    runtimeContext: { ctx, user: ctx.user, platform, runtime: true, ...extraRuntime }
  });
  return rendered.rendered;
}

function buildMessageInfo(ctx, raw) {
  if (ctx.platform === "discord" && ctx.meta?.message) {
    const message = ctx.meta.message;
    return {
      id: message.id,
      content: raw,
      channelId: message.channelId,
      guildId: message.guildId
    };
  }
  if (ctx.platform === "twitch") {
    return {
      channel: ctx.meta?.channel,
      content: raw
    };
  }
  if (ctx.platform === "youtube") {
    return {
      liveChatId: ctx.meta?.liveChatId,
      messageId: ctx.meta?.messageId,
      channelId: ctx.meta?.author?.channelId,
      content: raw
    };
  }
  return { content: raw };
}

async function safeReply(reply, content) {
  try {
    await reply(content);
  } catch (error) {
    commandLog.error("Command reply failed", error, { event: "command_reply_failed" });
  }
}

function recordCommandUsage(commandId) {
  if (!commandId) {
    return;
  }
  const now = Date.now();
  db.prepare(
    "INSERT INTO command_usage (command_id, count, updated_at) VALUES (?, 1, ?) " +
      "ON CONFLICT(command_id) DO UPDATE SET count = count + 1, updated_at = excluded.updated_at"
  ).run(commandId, now);
}

module.exports = {
  createCommandRouter
};
