const { app, BrowserWindow, ipcMain, dialog } = require("electron");
const path = require("path");
const fs = require("fs");
const { execFileSync } = require("child_process");
const { initStore } = require("./store");
const { TelegramManager } = require("./telegram");
const { Scheduler } = require("./scheduler");
const { TaskRunner } = require("./taskRunner");

let mainWindow;
let store;
let telegram;
let scheduler;
const taskRunners = new Map();

const formatTimestamp = (value) => {
  if (!value) return "—";
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return String(value);
  return date.toLocaleString("ru-RU");
};

const filterTaskRolesByAccounts = (taskId, roles, accounts) => {
  const accountMap = new Map(accounts.map((acc) => [acc.id, acc]));
  const filtered = [];
  let removedMissing = 0;
  let removedError = 0;
  roles.forEach((row) => {
    const account = accountMap.get(row.account_id);
    if (!account) {
      removedMissing += 1;
      return;
    }
    if (account.status && account.status !== "ok") {
      removedError += 1;
      return;
    }
    filtered.push({
      accountId: row.account_id,
      roleMonitor: Boolean(row.role_monitor),
      roleInvite: Boolean(row.role_invite),
      roleConfirm: row.role_confirm != null ? Boolean(row.role_confirm) : Boolean(row.role_invite)
    });
  });
  if (removedMissing || removedError) {
    store.setTaskAccountRoles(taskId, filtered);
  }
  return { filtered, removedMissing, removedError };
};

const startTaskWithChecks = async (id) => {
  const task = store.getTask(id);
  if (!task) return { ok: false, error: "Task not found" };
  const competitors = store.listTaskCompetitors(id).map((row) => row.link);
  const taskAccounts = store.listTaskAccounts(id);
  const existingAccounts = store.listAccounts();
  const filteredResult = filterTaskRolesByAccounts(id, taskAccounts, existingAccounts);
  const filteredRoles = filteredResult.filtered;
  const inviteIds = filteredRoles.filter((row) => row.roleInvite).map((row) => row.accountId);
  const monitorIds = filteredRoles.filter((row) => row.roleMonitor).map((row) => row.accountId);
  if (!inviteIds.length) {
    return { ok: false, error: "Нет аккаунтов с ролью инвайта." };
  }
  if (!monitorIds.length) {
    return { ok: false, error: "Нет аккаунтов с ролью мониторинга." };
  }
  const accessCheck = await telegram.checkGroupAccess(competitors, task.our_group);
  if (accessCheck && accessCheck.ok) {
    const ourAccess = accessCheck.result.find((item) => item.type === "our");
    if (ourAccess && !ourAccess.ok) {
      return { ok: false, error: `Нет доступа к нашей группе: ${ourAccess.details || ourAccess.value}` };
    }
  }
  const inviteAccess = await telegram.checkInvitePermissions(task, inviteIds);
  if (inviteAccess && inviteAccess.ok) {
    store.setTaskInviteAccess(id, inviteAccess.result || []);
    const canInvite = (inviteAccess.result || []).filter((row) => row.canInvite);
    if (!canInvite.length && !task.allow_start_without_invite_rights) {
      return { ok: false, error: "Нет аккаунтов с правами инвайта в нашей группе." };
    }
  } else if (inviteAccess && inviteAccess.error) {
    return { ok: false, error: inviteAccess.error };
  }
  if (task.invite_via_admins) {
    if (!task.invite_admin_master_id) {
      return { ok: false, error: "Не выбран главный аккаунт для инвайта через админов." };
    }
    const adminPrep = await telegram.prepareInviteAdmins(task, task.invite_admin_master_id, inviteIds);
    if (adminPrep && !adminPrep.ok) {
      return { ok: false, error: adminPrep.error || "Не удалось подготовить права админов." };
    }
  }

  let runner = taskRunners.get(id);
  if (!runner) {
    runner = new TaskRunner(store, telegram, task);
    taskRunners.set(id, runner);
  } else {
    runner.task = task;
  }
  store.setTaskStopReason(id, "");
  store.addAccountEvent(0, "", "task_start", `задача ${id}: запуск`);
  await runner.start();
  const warnings = [];
  if (accessCheck && accessCheck.ok) {
    const competitorIssues = accessCheck.result.filter((item) => item.type === "competitor" && !item.ok);
    if (competitorIssues.length) {
      const list = competitorIssues.map((item) => item.title || item.value).join(", ");
      warnings.push(`Нет доступа к конкурентам: ${list}.`);
    }
  }
  if (inviteAccess && inviteAccess.ok) {
    const missingSessions = (inviteAccess.result || []).filter((row) => !row.ok || row.reason === "Сессия не подключена");
    if (missingSessions.length) {
      warnings.push(`Сессии не подключены: ${missingSessions.length} аккаунт(ов).`);
    }
    if (task.invite_via_admins) {
      const noRights = (inviteAccess.result || []).filter((row) => !row.canInvite);
      if (noRights.length) {
        warnings.push(`Нет прав инвайта у ${noRights.length} аккаунт(ов).`);
      }
    }
  }
  if (task.invite_via_admins) {
    warnings.push("Режим инвайта через админов включен.");
  }
  if (filteredResult.removedError) {
    warnings.push(`Отключены аккаунты с ошибкой: ${filteredResult.removedError}.`);
  }
  if (filteredResult.removedMissing) {
    warnings.push(`Удалены отсутствующие аккаунты: ${filteredResult.removedMissing}.`);
  }
  store.addTaskAudit(id, "start", warnings.length ? JSON.stringify({ warnings }) : "");
  return { ok: true, warnings };
};

function createWindow() {
  const iconPath = path.join(__dirname, "..", "..", "resources", "icon.png");
  mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    icon: iconPath,
    webPreferences: {
      preload: path.join(__dirname, "preload.js"),
      contextIsolation: true,
      nodeIntegration: false
    }
  });

  const devUrl = process.env.VITE_DEV_SERVER_URL || "http://127.0.0.1:5173";
  if (app.isPackaged) {
    mainWindow.loadFile(path.join(__dirname, "..", "..", "dist", "index.html"));
  } else {
    mainWindow.loadURL(devUrl);
  }
}

async function bootstrap() {
  store = initStore(app.getPath("userData"));
  telegram = new TelegramManager(store);
  try {
    await telegram.init();
  } catch (error) {
    console.error("Failed to initialize Telegram clients:", error);
  }
  scheduler = new Scheduler(store, telegram);
}

app.whenReady().then(async () => {
  await bootstrap();
  createWindow();

  app.on("activate", () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});

app.on("window-all-closed", () => {
  if (process.platform !== "darwin") app.quit();
});

ipcMain.handle("settings:get", () => store.getSettings());
ipcMain.handle("settings:save", (_event, settings) => store.saveSettings(settings));

ipcMain.handle("accounts:list", () => store.listAccounts());
ipcMain.handle("accounts:resetCooldown", async (_event, accountId) => {
  store.clearAccountCooldown(accountId);
  store.addAccountEvent(accountId, "", "manual_reset", "Cooldown reset by user");
  return { ok: true };
});
ipcMain.handle("accounts:delete", async (_event, accountId) => {
  await telegram.removeAccount(accountId);
  store.deleteAccount(accountId);
  store.addAccountEvent(accountId, "", "delete", "Account deleted by user");
  return { ok: true };
});
ipcMain.handle("db:clear", async () => {
  for (const runner of taskRunners.values()) {
    runner.stop();
  }
  taskRunners.clear();
  const accounts = store.listAccounts();
  for (const account of accounts) {
    await telegram.removeAccount(account.id);
  }
  store.clearAllData();
  return { ok: true };
});
ipcMain.handle("sessions:reset", async () => {
  for (const runner of taskRunners.values()) {
    runner.stop();
  }
  taskRunners.clear();
  telegram.resetAllSessions();
  return { ok: true };
});
ipcMain.handle("accounts:startLogin", async (_event, payload) => {
  const result = await telegram.startLogin(payload);
  return result;
});
ipcMain.handle("accounts:completeLogin", async (_event, payload) => {
  const result = await telegram.completeLogin(payload);
  return result;
});

ipcMain.handle("accounts:importTdata", async (_event, payload) => {
  const { canceled, filePaths } = await dialog.showOpenDialog({
    title: "Выберите папку tdata",
    properties: ["openDirectory", "multiSelections"]
  });
  if (canceled || !filePaths || !filePaths.length) return { ok: false, canceled: true };

  const platformDir = process.platform === "win32" ? "win" : "mac";
  const binaryName = process.platform === "win32" ? "tgconvertor.exe" : "tgconvertor";
  const devBinary = path.join(__dirname, "..", "..", "resources", "converter", platformDir, binaryName);
  const packagedBinary = path.join(process.resourcesPath, "converter", platformDir, binaryName);
  const binaryPath = app.isPackaged ? packagedBinary : devBinary;

  if (!fs.existsSync(binaryPath)) {
    return { ok: false, error: "Встроенный конвертер не найден. Соберите его через scripts/build-converter.*" };
  }

  const imported = [];
  const failed = [];
  const skipped = [];
  const assignedIds = [];
  let authKeyDuplicatedCount = 0;

  for (const chosenPath of filePaths) {
    let tdataPath = chosenPath;
    const tdataCandidate = path.join(tdataPath, "tdata");
    if (fs.existsSync(tdataCandidate) && fs.lstatSync(tdataCandidate).isDirectory()) {
      tdataPath = tdataCandidate;
    }
    if (path.basename(tdataPath) !== "tdata") {
      failed.push({ path: chosenPath, error: "Нужна папка tdata или папка с вложенной tdata." });
      continue;
    }

    let output = "";
    try {
      output = execFileSync(binaryPath, [tdataPath], { encoding: "utf8" });
    } catch (error) {
      failed.push({ path: chosenPath, error: "Не удалось запустить встроенный конвертер tdata." });
      continue;
    }

    const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
    const candidate = lines.find((line) => line.length > 50) || lines[lines.length - 1];
    if (!candidate) {
      failed.push({ path: chosenPath, error: "Не удалось получить строку сессии из tdata." });
      continue;
    }

    try {
      const result = await telegram.importTdataSession({
        sessionString: candidate,
        apiId: payload && payload.apiId,
        apiHash: payload && payload.apiHash
      });
      if (!result.ok) {
        if (result.error === "DUPLICATE_ACCOUNT") {
          skipped.push({ path: chosenPath, reason: "Дубликат", accountId: result.accountId });
          if (result.accountId) assignedIds.push(result.accountId);
          continue;
        }
        if (result.error && String(result.error).includes("AUTH_KEY_DUPLICATED")) {
          authKeyDuplicatedCount += 1;
          failed.push({ path: chosenPath, error: result.error });
          continue;
        }
        failed.push({ path: chosenPath, error: result.error || "Ошибка импорта" });
        continue;
      }
      imported.push({ path: chosenPath, accountId: result.accountId });
      if (result.accountId) assignedIds.push(result.accountId);
    } catch (error) {
      const errorText = error.message || String(error);
      if (String(errorText).includes("AUTH_KEY_DUPLICATED")) {
        authKeyDuplicatedCount += 1;
        failed.push({ path: chosenPath, error: errorText });
        continue;
      }
      failed.push({ path: chosenPath, error: errorText });
    }
  }

  if (payload && payload.taskId && assignedIds.length) {
    const task = store.getTask(payload.taskId);
    if (task) {
      const existing = store.listTaskAccounts(payload.taskId).map((row) => row.account_id);
      const merged = Array.from(new Set([...(existing || []), ...assignedIds]));
      store.setTaskAccounts(payload.taskId, merged);
    }
  }

  if (authKeyDuplicatedCount > 0) {
    telegram.resetAllSessions();
  }
  return { ok: true, imported, skipped, failed, authKeyDuplicatedCount };
});

ipcMain.handle("logs:list", (_event, payload) => {
  if (payload && typeof payload === "object") {
    return store.listLogs(payload.limit || 100, payload.taskId);
  }
  return store.listLogs(payload || 100);
});
ipcMain.handle("invites:importFile", async (_event, payload) => {
  const taskId = payload && payload.taskId ? Number(payload.taskId) : 0;
  if (!taskId) return { ok: false, error: "Task not selected" };
  const onlyIds = Boolean(payload && payload.onlyIds);
  const sourceChat = payload && payload.sourceChat ? String(payload.sourceChat).trim() : "";
  if (onlyIds && !sourceChat) {
    return { ok: false, error: "Источник обязателен для файла только с ID" };
  }

  const { canceled, filePaths } = await dialog.showOpenDialog({
    title: "Выберите txt файл с пользователями",
    properties: ["openFile", "multiSelections"],
    filters: [{ name: "Text", extensions: ["txt"] }]
  });
  if (canceled || !filePaths || !filePaths.length) return { ok: false, canceled: true };

  const imported = [];
  const skipped = [];
  const failed = [];
  const targetSource = sourceChat || `file:${taskId}`;

  const parseLine = (line) => {
    const trimmed = line.trim();
    if (!trimmed) return null;
    if (onlyIds) {
      const id = trimmed.replace(/[^\d]/g, "");
      return id ? { userId: id, username: "" } : null;
    }
    if (trimmed.startsWith("@")) {
      const name = trimmed.replace(/^@+/, "").trim();
      return name ? { userId: name, username: name } : null;
    }
    if (/^\d+$/.test(trimmed)) {
      return { userId: trimmed, username: "" };
    }
    const urlMatch = trimmed.match(/t\.me\/([A-Za-z0-9_]+)/i);
    if (urlMatch) {
      const name = urlMatch[1];
      return name ? { userId: name, username: name } : null;
    }
    return { userId: trimmed, username: trimmed };
  };

  for (const filePath of filePaths) {
    try {
      const content = fs.readFileSync(filePath, "utf8");
      const lines = content.split(/\r?\n/);
      for (const line of lines) {
        const parsed = parseLine(line);
        if (!parsed) continue;
        const ok = store.enqueueInvite(
          taskId,
          parsed.userId,
          parsed.username,
          targetSource,
          "",
          0
        );
        if (ok) {
          imported.push(parsed.userId);
        } else {
          skipped.push(parsed.userId);
        }
      }
    } catch (error) {
      failed.push({ path: filePath, error: error.message || String(error) });
    }
  }

  if (imported.length) {
    store.addTaskAudit(taskId, "import_list", `Импорт из файла: ${imported.length}`);
  }

  return { ok: true, importedCount: imported.length, skippedCount: skipped.length, failed };
});
ipcMain.handle("invites:list", (_event, payload) => {
  if (payload && typeof payload === "object") {
    return store.listInvites(payload.limit || 200, payload.taskId);
  }
  return store.listInvites(payload || 200);
});
ipcMain.handle("logs:clear", (_event, taskId) => {
  store.clearLogs(taskId);
  return { ok: true };
});
ipcMain.handle("invites:clear", (_event, taskId) => {
  store.clearInvites(taskId);
  return { ok: true };
});
ipcMain.handle("queue:clear", (_event, taskId) => {
  store.clearQueue(taskId);
  return { ok: true };
});

ipcMain.handle("tasks:list", () => store.listTasks());
ipcMain.handle("tasks:get", (_event, id) => {
  const task = store.getTask(id);
  if (!task) return null;
  return {
    task,
    competitors: store.listTaskCompetitors(id).map((row) => row.link),
    accountIds: store.listTaskAccounts(id).map((row) => row.account_id),
    accountRoles: store.listTaskAccounts(id).map((row) => ({
      accountId: row.account_id,
      roleMonitor: Boolean(row.role_monitor),
      roleInvite: Boolean(row.role_invite),
      roleConfirm: row.role_confirm != null ? Boolean(row.role_confirm) : Boolean(row.role_invite)
    }))
  };
});
ipcMain.handle("tasks:save", (_event, payload) => {
  const existing = payload.task.id ? store.getTask(payload.task.id) : null;
  const taskId = store.saveTask(payload.task);
  store.setTaskCompetitors(taskId, payload.competitors || []);
  if (payload.accountRoles && payload.accountRoles.length) {
    store.setTaskAccountRoles(taskId, payload.accountRoles);
  } else {
    store.setTaskAccounts(taskId, payload.accountIds || []);
  }
  if (!existing) {
    store.addTaskAudit(taskId, "create", JSON.stringify({ name: payload.task.name, ourGroup: payload.task.ourGroup }));
  } else {
    const changes = {};
    if (existing.name !== payload.task.name) changes.name = [existing.name, payload.task.name];
    if (existing.our_group !== payload.task.ourGroup) changes.ourGroup = [existing.our_group, payload.task.ourGroup];
    if (existing.daily_limit !== payload.task.dailyLimit) changes.dailyLimit = [existing.daily_limit, payload.task.dailyLimit];
    if (existing.min_interval_minutes !== payload.task.minIntervalMinutes || existing.max_interval_minutes !== payload.task.maxIntervalMinutes) {
      changes.intervals = [
        `${existing.min_interval_minutes}-${existing.max_interval_minutes}`,
        `${payload.task.minIntervalMinutes}-${payload.task.maxIntervalMinutes}`
      ];
    }
    if (existing.history_limit !== payload.task.historyLimit) changes.historyLimit = [existing.history_limit, payload.task.historyLimit];
    if (existing.max_invites_per_cycle !== Number(payload.task.maxInvitesPerCycle || 0)) {
      changes.maxInvitesPerCycle = [existing.max_invites_per_cycle, Number(payload.task.maxInvitesPerCycle || 0)];
    }
    if (existing.allow_start_without_invite_rights !== (payload.task.allowStartWithoutInviteRights ? 1 : 0)) {
      changes.allowStartWithoutInviteRights = [Boolean(existing.allow_start_without_invite_rights), Boolean(payload.task.allowStartWithoutInviteRights)];
    }
    if (existing.parse_participants !== (payload.task.parseParticipants ? 1 : 0)) {
      changes.parseParticipants = [Boolean(existing.parse_participants), Boolean(payload.task.parseParticipants)];
    }
    if (existing.invite_via_admins !== (payload.task.inviteViaAdmins ? 1 : 0)) {
      changes.inviteViaAdmins = [Boolean(existing.invite_via_admins), Boolean(payload.task.inviteViaAdmins)];
    }
    if ((existing.invite_admin_master_id || 0) !== Number(payload.task.inviteAdminMasterId || 0)) {
      changes.inviteAdminMasterId = [existing.invite_admin_master_id || 0, Number(payload.task.inviteAdminMasterId || 0)];
    }
    if (existing.invite_admin_allow_flood !== (payload.task.inviteAdminAllowFlood ? 1 : 0)) {
      changes.inviteAdminAllowFlood = [Boolean(existing.invite_admin_allow_flood), Boolean(payload.task.inviteAdminAllowFlood)];
    }
    if (existing.invite_admin_anonymous !== (payload.task.inviteAdminAnonymous ? 1 : 0)) {
      changes.inviteAdminAnonymous = [Boolean(existing.invite_admin_anonymous), Boolean(payload.task.inviteAdminAnonymous)];
    }
    if (existing.separate_confirm_roles !== (payload.task.separateConfirmRoles ? 1 : 0)) {
      changes.separateConfirmRoles = [Boolean(existing.separate_confirm_roles), Boolean(payload.task.separateConfirmRoles)];
    }
    if (existing.max_confirm_bots !== Number(payload.task.maxConfirmBots || 0)) {
      changes.maxConfirmBots = [existing.max_confirm_bots, Number(payload.task.maxConfirmBots || 0)];
    }
    if (existing.warmup_enabled !== (payload.task.warmupEnabled ? 1 : 0)) {
      changes.warmupEnabled = [Boolean(existing.warmup_enabled), Boolean(payload.task.warmupEnabled)];
    }
    if (existing.warmup_start_limit !== Number(payload.task.warmupStartLimit || 0)) {
      changes.warmupStartLimit = [existing.warmup_start_limit, Number(payload.task.warmupStartLimit || 0)];
    }
    if (existing.warmup_daily_increase !== Number(payload.task.warmupDailyIncrease || 0)) {
      changes.warmupDailyIncrease = [existing.warmup_daily_increase, Number(payload.task.warmupDailyIncrease || 0)];
    }
    if (existing.cycle_competitors !== (payload.task.cycleCompetitors ? 1 : 0)) {
      changes.cycleCompetitors = [Boolean(existing.cycle_competitors), Boolean(payload.task.cycleCompetitors)];
    }
    if (existing.invite_link_on_fail !== (payload.task.inviteLinkOnFail ? 1 : 0)) {
      changes.inviteLinkOnFail = [Boolean(existing.invite_link_on_fail), Boolean(payload.task.inviteLinkOnFail)];
    }
    if (Object.keys(changes).length) {
      store.addTaskAudit(taskId, "update", JSON.stringify(changes));
    }
  }
  return { ok: true, taskId };
});
ipcMain.handle("tasks:delete", (_event, id) => {
  const runner = taskRunners.get(id);
  if (runner) {
    runner.stop();
    taskRunners.delete(id);
  }
  store.addTaskAudit(id, "delete", "");
  store.deleteTask(id);
  return { ok: true };
});
ipcMain.handle("tasks:start", async (_event, id) => {
  return startTaskWithChecks(id);
});
ipcMain.handle("tasks:stop", (_event, id) => {
  const runner = taskRunners.get(id);
  if (runner) {
    runner.stop();
    taskRunners.delete(id);
  }
  store.setTaskStopReason(id, "Остановлено пользователем");
  store.addTaskAudit(id, "stop", "Остановлено пользователем");
  store.addAccountEvent(0, "", "task_stop", `задача ${id}: остановлена пользователем`);
  return { ok: true };
});
ipcMain.handle("tasks:accountAssignments", () => {
  return store.listAllTaskAccounts();
});
ipcMain.handle("tasks:appendAccounts", (_event, payload) => {
  if (!payload || !payload.taskId) return { ok: false, error: "Task not found" };
  const task = store.getTask(payload.taskId);
  if (!task) return { ok: false, error: "Task not found" };
  const existingRows = store.listTaskAccounts(payload.taskId);
  const existing = new Map(existingRows.map((row) => [
    row.account_id,
    {
      accountId: row.account_id,
      roleMonitor: Boolean(row.role_monitor),
      roleInvite: Boolean(row.role_invite),
      roleConfirm: row.role_confirm != null ? Boolean(row.role_confirm) : Boolean(row.role_invite)
    }
  ]));
  (payload.accountIds || []).forEach((accountId) => {
    if (!existing.has(accountId)) {
      existing.set(accountId, { accountId, roleMonitor: true, roleInvite: true, roleConfirm: true });
    }
  });
  (payload.accountRoles || []).forEach((item) => {
    const roleConfirm = item.roleConfirm != null ? item.roleConfirm : item.roleInvite;
    existing.set(item.accountId, {
      accountId: item.accountId,
      roleMonitor: Boolean(item.roleMonitor),
      roleInvite: Boolean(item.roleInvite),
      roleConfirm: Boolean(roleConfirm)
    });
  });
  const merged = Array.from(existing.values());
  store.setTaskAccountRoles(payload.taskId, merged);
  return { ok: true, accountIds: merged.map((item) => item.accountId) };
});
ipcMain.handle("tasks:removeAccount", (_event, payload) => {
  if (!payload || !payload.taskId || !payload.accountId) {
    return { ok: false, error: "Task not found" };
  }
  const task = store.getTask(payload.taskId);
  if (!task) return { ok: false, error: "Task not found" };
  const existing = store.listTaskAccounts(payload.taskId)
    .filter((row) => row.account_id !== payload.accountId)
    .map((row) => ({
      accountId: row.account_id,
      roleMonitor: Boolean(row.role_monitor),
      roleInvite: Boolean(row.role_invite),
      roleConfirm: row.role_confirm != null ? Boolean(row.role_confirm) : Boolean(row.role_invite)
    }));
  store.setTaskAccountRoles(payload.taskId, existing);
  return { ok: true, accountIds: existing.map((item) => item.accountId) };
});
ipcMain.handle("tasks:startAll", async () => {
  const tasks = store.listTasks();
  let started = 0;
  let skipped = 0;
  const errors = [];
  for (const task of tasks) {
    if (!task.enabled) {
      skipped += 1;
      continue;
    }
    try {
      const result = await startTaskWithChecks(task.id);
      if (!result.ok) {
        errors.push({ id: task.id, error: result.error || "start failed" });
        continue;
      }
      started += 1;
    } catch (error) {
      errors.push({ id: task.id, error: error.message || String(error) });
    }
  }
  return { ok: errors.length === 0, started, skipped, errors };
});
ipcMain.handle("tasks:stopAll", () => {
  let stopped = 0;
  for (const [id, runner] of taskRunners.entries()) {
    runner.stop();
    taskRunners.delete(id);
    stopped += 1;
  }
  return { ok: true, stopped };
});
ipcMain.handle("tasks:status", (_event, id) => {
  const runner = taskRunners.get(id);
  const queueCount = store.getPendingCount(id);
  const dailyUsed = store.countInvitesToday(id);
  const unconfirmedCount = store.countInvitesByStatus(id, "unconfirmed");
  const task = store.getTask(id);
  const monitorInfo = telegram.getTaskMonitorInfo(id);
  const warnings = [];
  const readiness = { ok: true, reasons: [] };
  if (task) {
    const accountRows = store.listTaskAccounts(id);
    const accounts = store.listAccounts();
    const accountsById = new Map(accounts.map((acc) => [acc.id, acc]));
    if (runner && runner.isRunning()) {
      const sanitized = filterTaskRolesByAccounts(id, accountRows, accounts);
      if (sanitized.removedError || sanitized.removedMissing) {
        warnings.push(`Авто-синхронизация ролей: удалено ${sanitized.removedMissing + sanitized.removedError} аккаунт(ов).`);
      }
      if (!sanitized.filtered.length) {
        warnings.push("Задача остановлена: нет доступных аккаунтов.");
        store.setTaskStopReason(id, "Нет доступных аккаунтов");
        runner.stop();
      }
    }
    const inviteRows = accountRows.filter((row) => row.role_invite);
    const monitorRows = accountRows.filter((row) => row.role_monitor);
    if (!inviteRows.length) {
      readiness.ok = false;
      readiness.reasons.push("Нет аккаунтов с ролью инвайта.");
    }
    if (!monitorRows.length) {
      readiness.ok = false;
      readiness.reasons.push("Нет аккаунтов с ролью мониторинга.");
    }
    if (task.require_same_bot_in_both) {
      const hasSame = accountRows.some((row) => row.role_monitor && row.role_invite);
      if (!hasSame) {
        warnings.push("Режим “один бот в обоих группах” включен, но нет аккаунта с обеими ролями.");
        readiness.ok = false;
        readiness.reasons.push("Нет аккаунта с двумя ролями в режиме “один бот”.");
      }
    }
    const allAssignments = store.listAllTaskAccounts();
    const accountTaskMap = new Map();
    allAssignments.forEach((row) => {
      if (!accountTaskMap.has(row.account_id)) accountTaskMap.set(row.account_id, new Set());
      accountTaskMap.get(row.account_id).add(row.task_id);
    });
    const seen = new Set();
    accountRows.forEach((row) => {
      const tasksForAccount = accountTaskMap.get(row.account_id);
      if (tasksForAccount && tasksForAccount.size > 1 && !seen.has(row.account_id)) {
        seen.add(row.account_id);
        const account = accountsById.get(row.account_id);
        const label = account
          ? `${account.phone || account.user_id || row.account_id}${account.username ? ` (@${account.username})` : ""}`
          : row.account_id;
        warnings.push(`Аккаунт ${label} используется в ${tasksForAccount.size} задачах. Лимиты действий/групп общие.`);
      }
    });

    if (inviteRows.length) {
      const inviteAccounts = inviteRows.map((row) => accountsById.get(row.account_id)).filter(Boolean);
      const badSessions = inviteAccounts.filter((acc) => acc.status && acc.status !== "ok");
      if (badSessions.length) {
        readiness.ok = false;
        readiness.reasons.push(`Есть аккаунты с ошибкой сессии: ${badSessions.length}.`);
      }
    }

    if (runner && runner.isRunning() && queueCount === 0) {
      if (!monitorInfo || !monitorInfo.monitoring) {
        warnings.push("Очередь пуста: мониторинг не активен.");
      } else if (!monitorInfo.groups || monitorInfo.groups.length === 0) {
        warnings.push("Очередь пуста: нет групп в мониторинге.");
      } else if (!monitorInfo.lastMessageAt) {
        warnings.push("Очередь пуста: новых сообщений пока нет.");
      } else {
        warnings.push(`Очередь пуста: последнее сообщение ${formatTimestamp(monitorInfo.lastMessageAt)}.`);
      }
    }

    if (task.our_group && (task.our_group.includes("joinchat/") || task.our_group.includes("t.me/+"))) {
      warnings.push("Целевая группа указана по инвайт-ссылке — доступ может быть ограничен.");
    }

    if (task.task_invite_access) {
      try {
        const parsed = JSON.parse(task.task_invite_access);
        if (Array.isArray(parsed) && parsed.length) {
          const total = parsed.length;
          const canInvite = parsed.filter((row) => row.canInvite).length;
          const disconnected = parsed.filter((row) => row.reason === "Сессия не подключена").length;
          const isChannel = parsed.some((row) => row.targetType === "channel");
          const checkedAt = task.task_invite_access_at || "";
          if (task.invite_via_admins) {
            warnings.push(`Права инвайта: ${canInvite}/${total} аккаунтов могут добавлять.${checkedAt ? ` Проверка: ${formatTimestamp(checkedAt)}.` : ""}`);
          }
          if (disconnected) {
            warnings.push(`Сессия не подключена: ${disconnected} аккаунт(ов).`);
          }
          if (isChannel && task.invite_via_admins) {
            warnings.push("Цель — канал: добавлять участников могут только админы.");
          }
          if (canInvite === 0 && task.invite_via_admins) {
            readiness.ok = false;
            readiness.reasons.push("Нет аккаунтов с правами инвайта.");
          }
        }
      } catch (error) {
        // ignore parsing errors
      }
    }
  }
  const effectiveLimit = task ? store.getEffectiveDailyLimit(task) : 0;
  return {
    running: runner ? runner.isRunning() : false,
    queueCount,
    dailyUsed,
    unconfirmedCount,
    dailyLimit: effectiveLimit,
    dailyRemaining: task ? Math.max(0, Number(effectiveLimit || 0) - dailyUsed) : 0,
    cycleCompetitors: task ? Boolean(task.cycle_competitors) : false,
    competitorCursor: task ? Number(task.competitor_cursor || 0) : 0,
    monitorInfo,
    nextRunAt: runner ? runner.getNextRunAt() : "",
    nextInviteAccountId: runner ? runner.getNextInviteAccountId() : 0,
    lastInviteAccountId: runner ? runner.getLastInviteAccountId() : 0,
    pendingStats: store.getPendingStats(id),
    warnings,
    readiness,
    lastStopReason: task ? task.last_stop_reason || "" : "",
    lastStopAt: task ? task.last_stop_at || "" : ""
  };
});

ipcMain.handle("tasks:parseHistory", async (_event, id) => {
  const task = store.getTask(id);
  if (!task) return { ok: false, error: "Task not found" };
  const competitors = store.listTaskCompetitors(id).map((row) => row.link);
  const accounts = store.listTaskAccounts(id).map((row) => row.account_id);
  return telegram.parseHistoryForTask(task, competitors, accounts);
});

ipcMain.handle("tasks:checkAccess", async (_event, id) => {
  const task = store.getTask(id);
  if (!task) return { ok: false, error: "Task not found" };
  const competitors = store.listTaskCompetitors(id).map((row) => row.link);
  return telegram.checkGroupAccess(competitors, task.our_group);
});

ipcMain.handle("tasks:membershipStatus", async (_event, id) => {
  const task = store.getTask(id);
  if (!task) return { ok: false, error: "Task not found" };
  const competitors = store.listTaskCompetitors(id).map((row) => row.link);
  return telegram.getMembershipStatus(competitors, task.our_group);
});

ipcMain.handle("tasks:checkInviteAccess", async (_event, id) => {
  const task = store.getTask(id);
  if (!task) return { ok: false, error: "Task not found" };
  const accountRows = store.listTaskAccounts(id).filter((row) => row.role_invite);
  const existingAccounts = store.listAccounts();
  const existingIds = new Set(existingAccounts.map((account) => account.id));
  const missing = accountRows.filter((row) => !existingIds.has(row.account_id));
  if (missing.length) {
    const filtered = accountRows
      .filter((row) => existingIds.has(row.account_id))
      .map((row) => ({
        accountId: row.account_id,
        roleMonitor: Boolean(row.role_monitor),
        roleInvite: Boolean(row.role_invite),
        roleConfirm: row.role_confirm != null ? Boolean(row.role_confirm) : Boolean(row.role_invite)
      }));
    store.setTaskAccountRoles(id, filtered);
  }
  const accountIds = accountRows
    .filter((row) => existingIds.has(row.account_id))
    .map((row) => row.account_id);
  const result = await telegram.checkInvitePermissions(task, accountIds);
  if (result && result.ok) {
    store.setTaskInviteAccess(id, result.result || []);
  }
  return result;
});

ipcMain.handle("tasks:groupVisibility", async (_event, id) => {
  const task = store.getTask(id);
  if (!task) return { ok: false, error: "Task not found" };
  const competitors = store.listTaskCompetitors(id).map((row) => row.link);
  const result = await telegram.getGroupVisibility(task, competitors);
  return { ok: true, result };
});

const toCsv = (rows, headers) => {
  const escape = (value) => {
    const text = value == null ? "" : String(value);
    if (text.includes("\"") || text.includes(",") || text.includes("\n")) {
      return `"${text.replace(/\"/g, "\"\"")}"`;
    }
    return text;
  };
  const lines = [headers.join(",")];
  rows.forEach((row) => {
    lines.push(headers.map((key) => escape(row[key])).join(","));
  });
  return lines.join("\n");
};

const explainInviteError = (error) => {
  if (!error) return "";
  if (error === "USER_ID_INVALID") {
    return "Пользователь удален/скрыт; access_hash невалиден для этой сессии; приглашение в канал/чат без валидной сущности.";
  }
  if (error === "CHAT_WRITE_FORBIDDEN") {
    return "Аккаунт не может приглашать: нет прав или он не участник группы.";
  }
  if (error === "USER_NOT_MUTUAL_CONTACT") {
    return "Пользователь не взаимный контакт для добавляющего аккаунта. Обычно это происходит, когда в группе/канале включена опция «добавлять могут только контакты» или у пользователя закрыт приём инвайтов.";
  }
  if (error === "USER_PRIVACY_RESTRICTED") {
    return "Приглашение запрещено пользователем: приватность не позволяет добавлять в группы.";
  }
  if (error === "USER_NOT_PARTICIPANT") {
    return "Аккаунт не состоит в целевой группе или канал приватный.";
  }
  if (error === "USER_BANNED_IN_CHANNEL") {
    return "Пользователь заблокирован в группе или канале назначения.";
  }
  if (error === "USER_BOT") {
    return "Бота нельзя приглашать как обычного пользователя.";
  }
  if (error === "USER_KICKED") {
    return "Пользователь был удален из группы ранее.";
  }
  if (error === "CHAT_ADMIN_REQUIRED") {
    return "Для добавления участников нужны права администратора.";
  }
  if (error === "CHANNEL_INVALID") {
    return "Цель не распознана как группа/канал для этого аккаунта (ссылка недоступна или сущность устарела).";
  }
  if (error === "USER_ALREADY_PARTICIPANT") {
    return "Пользователь уже состоит в целевой группе.";
  }
  if (error === "CHAT_MEMBER_ADD_FAILED") {
    return "Telegram отказал в добавлении пользователя. Возможные причины: пользователь недоступен для инвайта, неверные данные, ограничения чата или лимиты аккаунта.";
  }
  if (error === "INVITE_HASH_EXPIRED" || error === "INVITE_HASH_INVALID") {
    return "Инвайт-ссылка недействительна или истекла.";
  }
  if (error === "CHANNEL_PRIVATE") {
    return "Целевая группа/канал приватные и недоступны по ссылке.";
  }
  if (error === "AUTH_KEY_DUPLICATED") {
    return "Сессия используется в другом месте, Telegram отозвал ключ.";
  }
  if (error.includes("FLOOD") || error.includes("PEER_FLOOD")) {
    return "Ограничение Telegram по частоте действий.";
  }
  return "";
};

const extractErrorCode = (value) => {
  if (!value) return "";
  const text = String(value).trim();
  const split = text.split(/[:(]/, 1);
  return split && split[0] ? split[0].trim() : text;
};

ipcMain.handle("logs:export", async (_event, taskId) => {
  const { canceled, filePath } = await dialog.showSaveDialog({
    title: "Выгрузить логи",
    defaultPath: "logs.csv"
  });
  if (canceled || !filePath) return { ok: false, canceled: true };

  const logs = store.listLogs(1000, taskId).map((log) => ({
    taskId: log.taskId,
    startedAt: log.startedAt,
    finishedAt: log.finishedAt,
    invitedCount: log.invitedCount,
    unconfirmedCount: log.meta && log.meta.unconfirmedCount != null ? log.meta.unconfirmedCount : "",
    cycleLimit: log.meta && log.meta.cycleLimit ? log.meta.cycleLimit : "",
    queueCount: log.meta && log.meta.queueCount != null ? log.meta.queueCount : "",
    batchSize: log.meta && log.meta.batchSize != null ? log.meta.batchSize : "",
    successIds: JSON.stringify(log.successIds || []),
    errors: JSON.stringify(log.errors || []),
    errorsHuman: JSON.stringify((log.errors || []).map((value) => {
      const code = extractErrorCode(value);
      const explanation = explainInviteError(code);
      return explanation ? `${value} (${explanation})` : value;
    }))
  }));
  const csv = toCsv(logs, ["taskId", "startedAt", "finishedAt", "invitedCount", "unconfirmedCount", "cycleLimit", "queueCount", "batchSize", "successIds", "errors", "errorsHuman"]);
  fs.writeFileSync(filePath, csv, "utf8");
  return { ok: true, filePath };
});

ipcMain.handle("invites:export", async (_event, taskId) => {
  const { canceled, filePath } = await dialog.showSaveDialog({
    title: "Выгрузить историю инвайтов",
    defaultPath: "invites.csv"
  });
  if (canceled || !filePath) return { ok: false, canceled: true };

  const invites = store.listInvites(2000, taskId);
  const enriched = invites.map((invite) => {
    const errorCode = extractErrorCode(invite.error);
    const skippedCode = extractErrorCode(invite.skippedReason);
    const confirmCode = extractErrorCode(invite.confirmError);
    return {
      ...invite,
      errorHuman: explainInviteError(errorCode),
      skippedReasonHuman: explainInviteError(skippedCode),
      confirmErrorHuman: explainInviteError(confirmCode)
    };
  });
  const csv = toCsv(enriched, [
    "taskId",
    "invitedAt",
    "userId",
    "username",
    "status",
    "error",
    "errorHuman",
    "confirmed",
    "confirmError",
    "confirmErrorHuman",
    "accountId",
    "accountPhone",
    "watcherAccountId",
    "watcherPhone",
    "strategy",
    "strategyMeta",
    "sourceChat",
    "skippedReason",
    "skippedReasonHuman"
  ]);
  fs.writeFileSync(filePath, csv, "utf8");
  return { ok: true, filePath };
});
ipcMain.handle("invites:exportProblems", async (_event, taskId) => {
  const { canceled, filePath } = await dialog.showSaveDialog({
    title: "Выгрузить проблемные инвайты",
    defaultPath: "problem-invites.csv"
  });
  if (canceled || !filePath) return { ok: false, canceled: true };

  const all = store.listInvites(5000, taskId);
  const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000;
  const filtered = all.filter((invite) => {
    const time = invite.invitedAt ? new Date(invite.invitedAt).getTime() : 0;
    if (!Number.isFinite(time) || time < cutoff) return false;
    if (invite.status === "success") return false;
    if (invite.status === "unconfirmed") return true;
    if (!invite.error && !invite.skippedReason) return false;
    return true;
  }).map((invite) => ({
    userId: invite.userId,
    username: invite.username ? `@${invite.username}` : "",
    status: invite.status,
    error: invite.error || "",
    errorHuman: explainInviteError(extractErrorCode(invite.error)),
    skippedReason: invite.skippedReason || "",
    skippedReasonHuman: explainInviteError(extractErrorCode(invite.skippedReason)),
    confirmed: invite.confirmed,
    confirmError: invite.confirmError || "",
    confirmErrorHuman: explainInviteError(extractErrorCode(invite.confirmError)),
    invitedAt: invite.invitedAt,
    sourceChat: invite.sourceChat,
    targetChat: invite.targetChat
  }));

  const csv = toCsv(filtered, [
    "userId",
    "username",
    "status",
    "error",
    "errorHuman",
    "skippedReason",
    "skippedReasonHuman",
    "confirmed",
    "confirmError",
    "confirmErrorHuman",
    "invitedAt",
    "sourceChat",
    "targetChat"
  ]);
  fs.writeFileSync(filePath, csv, "utf8");
  return { ok: true, filePath };
});
ipcMain.handle("fallback:export", async (_event, taskId) => {
  const { canceled, filePath } = await dialog.showSaveDialog({
    title: "Выгрузить fallback список",
    defaultPath: "fallback.csv"
  });
  if (canceled || !filePath) return { ok: false, canceled: true };

  const rows = store.listFallback(5000, taskId);
  const csv = toCsv(rows, [
    "taskId",
    "createdAt",
    "userId",
    "username",
    "reason",
    "route",
    "status",
    "sourceChat",
    "targetChat"
  ]);
  fs.writeFileSync(filePath, csv, "utf8");
  return { ok: true, filePath };
});
ipcMain.handle("fallback:list", (_event, payload) => {
  if (payload && typeof payload === "object") {
    return store.listFallback(payload.limit || 500, payload.taskId);
  }
  return store.listFallback(payload || 500);
});
ipcMain.handle("fallback:update", (_event, payload) => {
  if (!payload || !payload.id) return { ok: false, error: "Missing id" };
  store.updateFallbackStatus(payload.id, payload.status || "done");
  return { ok: true };
});
ipcMain.handle("fallback:clear", (_event, taskId) => {
  store.clearFallback(taskId);
  return { ok: true };
});

ipcMain.handle("accounts:events", async (_event, limit) => {
  return store.listAccountEvents(limit || 200);
});
ipcMain.handle("accounts:eventAdd", (_event, payload) => {
  if (!payload) return { ok: false };
  store.addAccountEvent(payload.accountId || 0, payload.phone || "", payload.action || "custom", payload.details || "");
  return { ok: true };
});

ipcMain.handle("accounts:events:clear", async () => {
  store.clearAccountEvents();
  return { ok: true };
});

ipcMain.handle("tasks:audit", (_event, id) => {
  return store.listTaskAudit(id, 200);
});

ipcMain.handle("accounts:refreshIdentity", async () => {
  const accounts = store.listAccounts();
  for (const account of accounts) {
    await telegram.refreshAccountIdentity(account.id);
  }
  return { ok: true };
});

ipcMain.handle("task:start", async () => {
  const settings = store.getSettings();
  if (settings.autoJoinOurGroup) {
    await telegram.ensureJoinOurGroup(settings.ourGroup);
  }
  await telegram.joinGroupsForAllAccounts(settings.competitorGroups, settings.ourGroup, settings);
  const monitorResult = await telegram.startMonitoring(settings.competitorGroups);
  scheduler.start(settings);
  return { running: true, monitorErrors: monitorResult && monitorResult.errors ? monitorResult.errors : [] };
});

ipcMain.handle("task:stop", async () => {
  scheduler.stop();
  await telegram.stopMonitoring();
  return { running: false };
});

ipcMain.handle("status:get", () => {
  const settings = store.getSettings();
  const dailyUsed = store.countInvitesToday();
  const dailyRemaining = Math.max(0, Number(settings.dailyLimit || 0) - dailyUsed);
  const queueCount = store.getPendingCount();
  const accounts = store.listAccounts();
  const connectedSessions = telegram.getConnectedCount();
  const accountStats = accounts.map((account) => {
    const used = store.countInvitesTodayByAccount(account.id);
    const limit = Number(account.daily_limit || settings.accountDailyLimit || 0);
    const remaining = limit > 0 ? Math.max(0, limit - used) : null;
    return {
      id: account.id,
      usedToday: used,
      remainingToday: remaining,
      limit
    };
  });
  const monitorInfo = telegram.getMonitorInfo();
  return {
    running: scheduler ? scheduler.isRunning() : false,
    queueCount,
    dailyRemaining,
    dailyUsed,
    dailyLimit: Number(settings.dailyLimit || 0),
    connectedSessions,
    totalAccounts: accounts.length,
    accountStats,
    monitorInfo
  };
});

ipcMain.handle("task:parseHistory", async (_event, limit) => {
  const settings = store.getSettings();
  const result = await telegram.parseHistory(settings.competitorGroups, limit || settings.historyLimit);
  return result;
});

ipcMain.handle("accounts:membershipStatus", async () => {
  const settings = store.getSettings();
  const result = await telegram.getMembershipStatus(settings.competitorGroups, settings.ourGroup);
  return result;
});

ipcMain.handle("groups:checkAccess", async () => {
  const settings = store.getSettings();
  const result = await telegram.checkGroupAccess(settings.competitorGroups, settings.ourGroup);
  return result;
});
