const dayjs = require("dayjs");

class TaskRunner {
  constructor(store, telegram, task) {
    this.store = store;
    this.telegram = telegram;
    this.task = task;
    this.running = false;
    this.timer = null;
    this.nextRunAt = "";
    this.nextInviteAccountId = 0;
    this.lastInviteAccountId = 0;
  }

  isRunning() {
    return this.running;
  }

  getNextRunAt() {
    return this.nextRunAt || "";
  }

  getNextInviteAccountId() {
    return this.nextInviteAccountId || 0;
  }

  getLastInviteAccountId() {
    return this.lastInviteAccountId || 0;
  }

  async start() {
    if (this.running) return;
    this.running = true;
    await this._initMonitoring();
    this._scheduleNext();
  }

  stop() {
    this.running = false;
    if (this.timer) clearTimeout(this.timer);
    this.timer = null;
    this.nextRunAt = "";
    this.nextInviteAccountId = 0;
    this.telegram.stopTaskMonitor(this.task.id);
  }

  async _initMonitoring() {
    const competitors = this.store.listTaskCompetitors(this.task.id).map((row) => row.link);
    const accountRows = this.store.listTaskAccounts(this.task.id);
    const accounts = accountRows.map((row) => row.account_id);
    const monitorIds = accountRows.filter((row) => row.role_monitor).map((row) => row.account_id);
    const inviteIds = accountRows.filter((row) => row.role_invite).map((row) => row.account_id);
    await this.telegram.joinGroupsForTask(this.task, competitors, accounts, {
      monitorIds,
      inviteIds
    });
    await this.telegram.startTaskMonitor(this.task, competitors, accounts, monitorIds);
  }

  _scheduleNext() {
    if (!this.running) return;
    const minMs = Number(this.task.min_interval_minutes || 5) * 60 * 1000;
    const maxMs = Number(this.task.max_interval_minutes || 10) * 60 * 1000;
    const jitter = Math.max(minMs, Math.min(maxMs, minMs + Math.random() * (maxMs - minMs)));
    this.nextRunAt = new Date(Date.now() + jitter).toISOString();
    this.timer = setTimeout(() => this._runBatch(), jitter);
  }

  async _runBatch() {
    const startedAt = dayjs().toISOString();
    const errors = [];
    const successIds = [];
    let invitedCount = 0;
    this.nextRunAt = "";
    this.nextInviteAccountId = 0;

    try {
      const settings = this.store.getSettings();
      const ttlHours = Number(settings.queueTtlHours || 0);
      if (ttlHours > 0) {
        this.store.clearQueueOlderThan(this.task.id, ttlHours);
      }
      const accounts = this.store.listTaskAccounts(this.task.id).map((row) => row.account_id);
      let inviteAccounts = accounts;
      const roles = this.telegram.getTaskRoleAssignments(this.task.id);
      const hasExplicitRoles = roles && ((roles.ourIds || []).length || (roles.competitorIds || []).length);
      if (hasExplicitRoles) {
        inviteAccounts = (roles.ourIds || []).length ? roles.ourIds : (roles.competitorIds || []);
        if (!inviteAccounts.length) {
          errors.push("No invite accounts (role-based)");
        }
      } else if (this.task.separate_bot_roles || this.task.require_same_bot_in_both) {
        inviteAccounts = this.task.require_same_bot_in_both
          ? (roles.competitorIds || [])
          : (roles.ourIds || []);
        if (!inviteAccounts.length) {
          errors.push(this.task.require_same_bot_in_both ? "No invite accounts (same bot required)" : "No invite accounts (separated roles)");
        }
      } else {
        const limit = Math.max(1, Number(this.task.max_our_bots || accounts.length || 1));
        if (inviteAccounts.length > limit) {
          inviteAccounts = inviteAccounts.slice(0, limit);
        }
      }
      if (!accounts.length) {
        errors.push("No accounts assigned");
      }
      if (!this.task.multi_accounts_per_run) {
        const entry = this.telegram.pickInviteAccount(inviteAccounts, Boolean(this.task.random_accounts));
        inviteAccounts = entry ? [entry.account.id] : [];
        this.nextInviteAccountId = entry ? entry.account.id : 0;
      } else if (inviteAccounts.length) {
        this.nextInviteAccountId = inviteAccounts[0];
      }
      const totalAccounts = accounts.length;
      if (this.task.stop_on_blocked) {
        const all = this.store.listAccounts().filter((acc) => accounts.includes(acc.id));
        const blocked = all.filter((acc) => acc.status !== "ok").length;
        const percent = totalAccounts ? Math.round((blocked / totalAccounts) * 100) : 0;
        if (percent >= Number(this.task.stop_blocked_percent || 25)) {
          errors.push(`Stopped: blocked ${percent}% >= ${this.task.stop_blocked_percent}%`);
          this.stop();
        }
      }

      const dailyLimit = Number(this.task.daily_limit || 100);
      const alreadyInvited = this.store.countInvitesToday(this.task.id);
      if (alreadyInvited >= dailyLimit) {
        errors.push("Daily limit reached");
      } else {
        const remaining = dailyLimit - alreadyInvited;
        const batchSize = Math.min(20, remaining);
        const pending = this.store.getPendingInvites(this.task.id, batchSize);
        const accountMap = new Map(this.store.listAccounts().map((account) => [account.id, account]));
        if (!inviteAccounts.length && pending.length) {
          errors.push("No available accounts under limits");
        }

        for (const item of pending) {
          if (item.attempts >= 2 && this.task.retry_on_fail) {
            this.store.markInviteStatus(item.id, "failed");
            continue;
          }
          let accountsForInvite = inviteAccounts;
          if (item.watcher_account_id && !inviteAccounts.includes(item.watcher_account_id)) {
            accountsForInvite = [item.watcher_account_id];
          }
          const watcherAccount = accountMap.get(item.watcher_account_id || 0);
          const result = await this.telegram.inviteUserForTask(this.task, item.user_id, accountsForInvite, {
            randomize: Boolean(this.task.random_accounts),
            userAccessHash: item.user_access_hash,
            username: item.username,
            sourceChat: item.source_chat
          });
          if (result.ok) {
            invitedCount += 1;
            successIds.push(item.user_id);
            this.store.markInviteStatus(item.id, "invited");
            this.lastInviteAccountId = result.accountId || this.lastInviteAccountId;
            this.store.recordInvite(
              this.task.id,
              item.user_id,
              item.username,
              result.accountId,
              result.accountPhone,
              item.source_chat,
              "success",
              "",
              "",
              "invite",
              item.user_access_hash,
              watcherAccount ? watcherAccount.id : 0,
              watcherAccount ? watcherAccount.phone : "",
              result.strategy,
              result.strategyMeta,
              this.task.our_group,
              result.targetType
            );
          } else {
            errors.push(`${item.user_id}: ${result.error}`);
            if (this.task.retry_on_fail) {
              this.store.incrementInviteAttempt(item.id);
              this.store.markInviteStatus(item.id, "pending");
            } else {
              this.store.markInviteStatus(item.id, "failed");
            }
            this.store.recordInvite(
              this.task.id,
              item.user_id,
              item.username,
              result.accountId,
              result.accountPhone,
              item.source_chat,
              "failed",
              result.error || "",
              result.error || "",
              "invite",
              item.user_access_hash,
              watcherAccount ? watcherAccount.id : 0,
              watcherAccount ? watcherAccount.phone : "",
              result.strategy,
              result.strategyMeta,
              this.task.our_group,
              result.targetType
            );
            let strategyLine = result.strategy || "—";
            if (result.strategyMeta) {
              try {
                const parsed = JSON.parse(result.strategyMeta);
                if (Array.isArray(parsed) && parsed.length) {
                  const steps = parsed
                    .map((step) => `${step.strategy}:${step.ok ? "ok" : "fail"}`)
                    .join(", ");
                  strategyLine = `${strategyLine} (${steps})`;
                }
              } catch (error) {
                // ignore parse errors
              }
            }
            const detailed = [
              `Пользователь: ${item.user_id || "—"}`,
              `Ошибка: ${result.error || "unknown"}`,
              `Стратегия: ${strategyLine}`,
              `Источник: ${item.source_chat || "—"}`,
              `Цель: ${this.task.our_group || "—"}`,
              `Тип цели: ${result.targetType || "—"}`,
              `Аккаунт: ${result.accountPhone || result.accountId || "—"}`
            ].join("\n");
            this.store.addAccountEvent(
              watcherAccount ? watcherAccount.id : 0,
              watcherAccount ? watcherAccount.phone : "",
              "invite_failed",
              detailed
            );
          }
        }
        if (!pending.length) {
          errors.push("queue empty");
        }
      }
    } catch (error) {
      errors.push(error.message || String(error));
    }

    const finishedAt = dayjs().toISOString();
    this.store.addLog({
      taskId: this.task.id,
      startedAt,
      finishedAt,
      invitedCount,
      successIds,
      errors
    });

    this._scheduleNext();
  }
}

module.exports = { TaskRunner };
