const { TelegramClient, Api } = require("telegram");
const { StringSession } = require("telegram/sessions");
const { NewMessage } = require("telegram/events");

class TelegramManager {
  constructor(store) {
    this.store = store;
    this.clients = new Map();
    this.pendingLogins = new Map();
    this.monitorHandler = null;
    this.monitorClientId = null;
    this.monitorGroups = [];
    this.monitorTimer = null;
    this.monitorState = new Map();
    this.lastMonitorMessageAt = "";
    this.lastMonitorSource = "";
    this.taskMonitors = new Map();
    this.inviteIndex = 0;
    this.desktopApiId = 2040;
    this.desktopApiHash = "b18441a1ff607e10a989891a5462e627";
  }

  async init() {
    const accounts = this.store.listAccounts();
    for (const account of accounts) {
      await this._connectAccount(account);
    }
  }

  async _connectAccount(account) {
    const session = new StringSession(account.session);
    const client = new TelegramClient(session, Number(account.api_id || account.apiId), account.api_hash || account.apiHash, {
      connectionRetries: 3
    });
    await client.connect();
    try {
      const me = await client.getMe();
      if (me && me.id) {
        this.store.updateAccountIdentity(account.id, me.id.toString(), me.phone || account.phone || "");
        account.user_id = me.id.toString();
        if (me.phone) account.phone = me.phone;
      }
    } catch (error) {
      // ignore identity fetch errors
    }
    this.clients.set(account.id, { client, account });
  }

  async startLogin({ apiId, apiHash, phone }) {
    const session = new StringSession("");
    const client = new TelegramClient(session, Number(apiId), apiHash, {
      connectionRetries: 3
    });

    await client.connect();
    const sendResult = await client.invoke(
      new Api.auth.SendCode({
        phoneNumber: phone,
        apiId: Number(apiId),
        apiHash,
        settings: new Api.CodeSettings({})
      })
    );

    const loginId = `${phone}-${Date.now()}`;
    this.pendingLogins.set(loginId, {
      client,
      apiId: Number(apiId),
      apiHash,
      phone,
      phoneCodeHash: sendResult.phoneCodeHash
    });

    return { loginId };
  }

  async completeLogin({ loginId, code, password }) {
    const pending = this.pendingLogins.get(loginId);
    if (!pending) {
      return { ok: false, error: "Login session not found" };
    }

    const { client, apiId, apiHash, phone, phoneCodeHash } = pending;
    try {
      await client.invoke(
        new Api.auth.SignIn({
          phoneNumber: phone,
          phoneCodeHash,
          phoneCode: code
        })
      );
    } catch (error) {
      if (error.errorMessage === "SESSION_PASSWORD_NEEDED") {
        if (!password) {
          return { ok: false, error: "PASSWORD_REQUIRED" };
        }
        await client.checkPassword(password);
      } else {
        return { ok: false, error: error.message || String(error) };
      }
    }

    const sessionString = client.session.save();
    const settings = this.store.getSettings();
    const me = await client.getMe();
    const userId = me && me.id ? me.id.toString() : "";
    const actualPhone = me && me.phone ? me.phone : phone;
    const existing = this.store.findAccountByIdentity({
      userId,
      phone: actualPhone,
      session: sessionString
    });
    if (existing) {
      this.pendingLogins.delete(loginId);
      try {
        await client.disconnect();
      } catch (error) {
        // ignore disconnect errors
      }
      return { ok: false, error: "DUPLICATE_ACCOUNT", accountId: existing.id };
    }
    const accountId = this.store.addAccount({
      phone: actualPhone,
      apiId,
      apiHash,
      session: sessionString,
      userId,
      maxGroups: settings.accountMaxGroups,
      dailyLimit: settings.accountDailyLimit,
      status: "ok",
      lastError: ""
    });

    this.clients.set(accountId, {
      client,
      account: {
        id: accountId,
        phone: actualPhone,
        api_id: apiId,
        api_hash: apiHash,
        user_id: userId,
        status: "ok",
        last_error: ""
      }
    });
    this.pendingLogins.delete(loginId);

    return { ok: true, accountId };
  }

  async importTdataSession({ sessionString, apiId, apiHash }) {
    const usedApiId = Number(apiId || this.desktopApiId);
    const usedApiHash = apiHash || this.desktopApiHash;
    const session = new StringSession(sessionString);
    const client = new TelegramClient(session, usedApiId, usedApiHash, {
      connectionRetries: 3
    });
    await client.connect();
    const me = await client.getMe();
    const phone = me && me.phone ? me.phone : "unknown";
    const userId = me && me.id ? me.id.toString() : "";
    const existing = this.store.findAccountByIdentity({
      userId,
      phone,
      session: sessionString
    });
    if (existing) {
      try {
        await client.disconnect();
      } catch (error) {
        // ignore disconnect errors
      }
      return { ok: false, error: "DUPLICATE_ACCOUNT", accountId: existing.id };
    }

    const savedSession = client.session.save();
    const settings = this.store.getSettings();
    const accountId = this.store.addAccount({
      phone,
      apiId: usedApiId,
      apiHash: usedApiHash,
      session: savedSession,
      userId,
      maxGroups: settings.accountMaxGroups,
      dailyLimit: settings.accountDailyLimit,
      status: "ok",
      lastError: ""
    });

    this.clients.set(accountId, {
      client,
      account: {
        id: accountId,
        phone,
        api_id: usedApiId,
        api_hash: usedApiHash,
        user_id: userId,
        max_groups: settings.accountMaxGroups,
        daily_limit: settings.accountDailyLimit,
        status: "ok",
        last_error: ""
      }
    });

    return { ok: true, accountId };
  }

  async startMonitoring(competitorGroup) {
    const groups = Array.isArray(competitorGroup) ? competitorGroup.filter(Boolean) : [competitorGroup].filter(Boolean);
    if (!groups.length) return;
    if (this.monitorHandler) return;

    const accountEntry = this._pickClient();
    if (!accountEntry) return;

    this.monitorClientId = accountEntry.account.id;
    this.monitorGroups = groups;
    const client = accountEntry.client;

    const settings = this.store.getSettings();
    await this._autoJoinGroups(client, groups, settings.autoJoinCompetitors, accountEntry.account);

    const resolved = [];
    const errors = [];
    for (const group of groups) {
      const result = await this._resolveGroupEntity(client, group, settings.autoJoinCompetitors, accountEntry.account);
      if (result.ok) {
        resolved.push({ entity: result.entity, source: group });
      } else {
        errors.push(`${group}: ${result.error}`);
      }
    }
    if (!resolved.length) {
      return { ok: false, errors: errors.length ? errors : ["No groups to monitor"] };
    }

    this.monitorHandler = async (event) => {
      const sender = event.message.senderId;
      if (!sender) return;
      const userId = sender.toString();
      if (this._isOwnAccount(userId)) return;
      let senderEntity = null;
      try {
        senderEntity = await event.getSender();
      } catch (error) {
        senderEntity = null;
      }
      if (senderEntity && senderEntity.bot) return;
      const username = senderEntity && senderEntity.username ? senderEntity.username : "";
      const accessHash = senderEntity && senderEntity.accessHash ? senderEntity.accessHash.toString() : "";
      const sourceChat = event.message.chatId ? event.message.chatId.toString() : "unknown";
      if (!this.monitorState.has(sourceChat)) return;
      this.store.enqueueInvite(0, userId, username, sourceChat, accessHash);
      this.lastMonitorMessageAt = new Date().toISOString();
      this.lastMonitorSource = sourceChat;
    };

    this.monitorState.clear();
    resolved.forEach((item) => {
      const id = item.entity && item.entity.id != null ? item.entity.id.toString() : item.source;
      this.monitorState.set(id, { entity: item.entity, source: item.source, lastId: 0 });
    });
    client.addEventHandler(this.monitorHandler, new NewMessage({}));
    this._startMonitorPolling(client);
    return { ok: true, errors };
  }

  async stopMonitoring() {
    if (!this.monitorHandler || this.monitorClientId == null) return;
    const entry = this.clients.get(this.monitorClientId);
    if (entry) {
      entry.client.removeEventHandler(this.monitorHandler);
    }
    this.monitorHandler = null;
    this.monitorClientId = null;
    this.monitorGroups = [];
    this.monitorState.clear();
    if (this.monitorTimer) clearInterval(this.monitorTimer);
    this.monitorTimer = null;
  }

  async removeAccount(accountId) {
    const entry = this.clients.get(accountId);
    if (!entry) return;
    if (this.monitorClientId === accountId) {
      await this.stopMonitoring();
    }
    try {
      await entry.client.disconnect();
    } catch (error) {
      // ignore disconnect errors
    }
    this.clients.delete(accountId);
  }

  async refreshAccountIdentity(accountId) {
    const entry = this.clients.get(accountId);
    if (!entry) return;
    try {
      const me = await entry.client.getMe();
      if (me && me.id) {
        this.store.updateAccountIdentity(accountId, me.id.toString(), me.phone || entry.account.phone || "");
        entry.account.user_id = me.id.toString();
        if (me.phone) entry.account.phone = me.phone;
      }
    } catch (error) {
      // ignore identity refresh errors
    }
  }

  isMonitoring() {
    return Boolean(this.monitorHandler);
  }

  getMonitorInfo() {
    return {
      monitoring: this.isMonitoring(),
      accountId: this.monitorClientId || 0,
      groups: this.monitorGroups || [],
      lastMessageAt: this.lastMonitorMessageAt,
      lastSource: this.lastMonitorSource
    };
  }

  _pickClient() {
    const entries = Array.from(this.clients.values());
    if (!entries.length) return null;
    const ordered = entries.filter((entry) => entry.account.status === "ok" && !this._isInCooldown(entry.account));
    if (!ordered.length) return null;
    const entry = ordered[this.inviteIndex % ordered.length];
    this.inviteIndex += 1;
    return entry;
  }

  async inviteUser(targetGroup, userId) {
    const entry = this._pickClientForInvite();
    if (!entry) {
      return { ok: false, error: "No available accounts under limits", accountId: 0, accountPhone: "" };
    }

    const { client, account } = entry;
    try {
      const allowJoin = this.store.getSettings().autoJoinOurGroup;
      await this._autoJoinGroups(client, [targetGroup], allowJoin, account);
      const resolved = await this._resolveGroupEntity(client, targetGroup, allowJoin, account);
      if (!resolved.ok) {
        throw new Error(resolved.error);
      }
      const targetEntity = resolved.entity;
      const user = await client.getEntity(userId);

      if (targetEntity.className === "Channel") {
        await client.invoke(
          new Api.channels.InviteToChannel({
            channel: targetEntity,
            users: [user]
          })
        );
      } else if (targetEntity.className === "Chat") {
        await client.invoke(
          new Api.messages.AddChatUser({
            chatId: targetEntity.id,
            userId: user,
            fwdLimit: 0
          })
        );
      } else {
        throw new Error("Unsupported target chat type");
      }

      this.store.updateAccountStatus(account.id, "ok", "");
      return { ok: true, accountId: account.id, accountPhone: account.phone || "" };
    } catch (error) {
      const errorText = error.errorMessage || error.message || String(error);
      if (errorText.includes("FLOOD") || errorText.includes("PEER_FLOOD")) {
        this._applyFloodCooldown(account, errorText);
      } else {
        this.store.updateAccountStatus(account.id, account.status || "ok", errorText);
      }
      return { ok: false, error: errorText, accountId: account.id, accountPhone: account.phone || "" };
    }
  }

  async inviteUserForTask(task, userId, allowedAccountIds, options = {}) {
    const entry = this._pickClientForInvite(allowedAccountIds, options.randomize);
    if (!entry) {
      return { ok: false, error: "No available accounts under limits", accountId: 0, accountPhone: "" };
    }

    const { client, account } = entry;
    try {
      const accessHash = options.userAccessHash || "";
      const providedUsername = options.username || "";
      const allowJoin = Boolean(task.auto_join_our_group);
      await this._autoJoinGroups(client, [task.our_group], allowJoin, account);
      const resolved = await this._resolveGroupEntity(client, task.our_group, allowJoin, account);
      if (!resolved.ok) throw new Error(resolved.error);
      const targetEntity = resolved.entity;
      let user = null;
      if (accessHash) {
        try {
          user = new Api.InputUser({
            userId: BigInt(userId),
            accessHash: BigInt(accessHash)
          });
        } catch (error) {
          user = null;
        }
      }
      if (!user && providedUsername) {
        const username = providedUsername.startsWith("@") ? providedUsername : `@${providedUsername}`;
        try {
          user = await client.getEntity(username);
        } catch (error) {
          user = null;
        }
      }
      if (!user) {
        user = await client.getEntity(userId);
      }

      if (targetEntity.className === "Channel") {
        await client.invoke(
          new Api.channels.InviteToChannel({
            channel: targetEntity,
            users: [user]
          })
        );
      } else if (targetEntity.className === "Chat") {
        await client.invoke(
          new Api.messages.AddChatUser({
            chatId: targetEntity.id,
            userId: user,
            fwdLimit: 0
          })
        );
      } else {
        throw new Error("Unsupported target chat type");
      }

      this.store.updateAccountStatus(account.id, "ok", "");
      return { ok: true, accountId: account.id, accountPhone: account.phone || "" };
    } catch (error) {
      const errorText = error.errorMessage || error.message || String(error);
      if (errorText.includes("FLOOD") || errorText.includes("PEER_FLOOD")) {
        this._applyFloodCooldown(account, errorText);
      } else {
        this.store.updateAccountStatus(account.id, account.status || "ok", errorText);
      }
      return { ok: false, error: errorText, accountId: account.id, accountPhone: account.phone || "" };
    }
  }

  _pickClientForInvite(allowedAccountIds, randomize) {
    const entries = Array.from(this.clients.values()).filter((entry) => entry.account.status === "ok");
    if (!entries.length) return null;

    const settings = this.store.getSettings();
    const allowed = Array.isArray(allowedAccountIds) && allowedAccountIds.length
      ? entries.filter((entry) => allowedAccountIds.includes(entry.account.id))
      : entries;
    if (!allowed.length) return null;
    const eligible = allowed.filter((entry) => {
      if (this._isInCooldown(entry.account)) return false;
      const limit = Number(entry.account.daily_limit || settings.accountDailyLimit || 0);
      if (limit > 0) {
        const used = this.store.countInvitesTodayByAccount(entry.account.id);
        if (used >= limit) return false;
      }
      return true;
    });
    if (!eligible.length) return null;
    if (randomize) {
      const entry = eligible[Math.floor(Math.random() * eligible.length)];
      this.inviteIndex += 1;
      return entry;
    }
    for (let i = 0; i < allowed.length; i += 1) {
      const entry = allowed[(this.inviteIndex + i) % allowed.length];
      if (!eligible.includes(entry)) continue;
      this.inviteIndex += 1;
      return entry;
    }
    return eligible[0] || null;
  }

  _pickClientFromAllowed(allowedAccountIds) {
    const entries = Array.from(this.clients.values()).filter((entry) => entry.account.status === "ok");
    if (!entries.length) return null;
    const allowed = Array.isArray(allowedAccountIds) && allowedAccountIds.length
      ? entries.filter((entry) => allowedAccountIds.includes(entry.account.id))
      : entries;
    if (!allowed.length) return null;
    const available = allowed.filter((entry) => !this._isInCooldown(entry.account));
    return available[0] || null;
  }

  pickInviteAccount(allowedAccountIds, randomize) {
    return this._pickClientForInvite(allowedAccountIds, randomize);
  }

  async parseHistory(competitorGroups, limit) {
    const groups = Array.isArray(competitorGroups) ? competitorGroups.filter(Boolean) : [competitorGroups].filter(Boolean);
    if (!groups.length) return { ok: false, error: "No competitor groups" };

    const entry = this._pickClient();
    if (!entry) return { ok: false, error: "No available accounts" };

    const { client } = entry;
    const perGroupLimit = Math.max(1, Number(limit) || 200);
    await this._autoJoinGroups(client, groups, this.store.getSettings().autoJoinCompetitors, entry.account);
    const errors = [];
    for (const group of groups) {
      const resolved = await this._resolveGroupEntity(client, group, this.store.getSettings().autoJoinCompetitors, entry.account);
      if (!resolved.ok) {
        errors.push(`${group}: ${resolved.error}`);
        continue;
      }
      const messages = await client.getMessages(resolved.entity, { limit: perGroupLimit });
      for (const message of messages) {
        const senderId = message.senderId;
        if (!senderId) continue;
        let username = "";
        let accessHash = "";
        try {
          const sender = await message.getSender();
          if (sender && sender.bot) continue;
          username = sender && sender.username ? sender.username : "";
          accessHash = sender && sender.accessHash ? sender.accessHash.toString() : "";
        } catch (error) {
          username = "";
          accessHash = "";
        }
        this.store.enqueueInvite(0, senderId.toString(), username, group, accessHash);
      }
    }

    return { ok: true, errors };
  }

  async getMembershipStatus(competitorGroups, ourGroup) {
    const groups = Array.isArray(competitorGroups) ? competitorGroups.filter(Boolean) : [];
    const results = [];

    for (const entry of this.clients.values()) {
      const { client, account } = entry;

      let competitorCount = 0;
      for (const group of groups) {
        const isMember = await this._isParticipant(client, group);
        if (isMember) competitorCount += 1;
      }

      let ourGroupMember = false;
      if (ourGroup) {
        ourGroupMember = await this._isParticipant(client, ourGroup);
      }

      results.push({
        accountId: account.id,
        competitorCount,
        competitorTotal: groups.length,
        ourGroupMember
      });
    }

    return results;
  }

  async ensureJoinOurGroup(ourGroup) {
    if (!ourGroup) return { ok: false, error: "No target group" };
    const entry = this._pickClient();
    if (!entry) return { ok: false, error: "No available accounts" };
    await this._autoJoinGroups(entry.client, [ourGroup], true, entry.account);
    return { ok: true };
  }

  async checkGroupAccess(competitorGroups, ourGroup) {
    const groups = Array.isArray(competitorGroups) ? competitorGroups.filter(Boolean) : [];
    const result = [];
    const entry = this._pickClient();
    if (!entry) return { ok: false, error: "No available accounts" };

    const { client } = entry;
    const allGroups = [
      ...groups.map((value) => ({ type: "competitor", value })),
      ...(ourGroup ? [{ type: "our", value: ourGroup }] : [])
    ];

    for (const item of allGroups) {
      const value = item.value;
      try {
        if (this._isInviteLink(value)) {
          const hash = this._extractInviteHash(value);
          if (!hash) throw new Error("Invalid invite link");
          const check = await client.invoke(new Api.messages.CheckChatInvite({ hash }));
          const title = check && check.chat ? check.chat.title : "";
          result.push({
            type: item.type,
            value,
            ok: true,
            title,
            details: check.className || ""
          });
        } else {
          const entity = await client.getEntity(value);
          const title = entity && entity.title ? entity.title : "";
          result.push({
            type: item.type,
            value,
            ok: true,
            title,
            details: "entity"
          });
        }
      } catch (error) {
        result.push({
          type: item.type,
          value,
          ok: false,
          title: "",
          details: error.errorMessage || error.message || String(error)
        });
      }
    }

    return { ok: true, result };
  }

  async _autoJoinGroups(client, groups, enabled, account) {
    if (!enabled) return;
    const settings = this.store.getSettings();
    let maxGroups = Number(account && account.max_groups != null ? account.max_groups : settings.accountMaxGroups);
    if (!Number.isFinite(maxGroups) || maxGroups <= 0) {
      maxGroups = Number.POSITIVE_INFINITY;
    }
    let memberCount = 0;
    for (const group of groups) {
      if (!group) continue;
      try {
        if (memberCount >= maxGroups) break;
        const alreadyMember = await this._isParticipant(client, group);
        if (alreadyMember) {
          memberCount += 1;
          continue;
        }
        if (this._isInviteLink(group)) {
          const hash = this._extractInviteHash(group);
          if (hash) {
            await client.invoke(new Api.messages.ImportChatInvite({ hash }));
            memberCount += 1;
          }
        } else {
          const entity = await client.getEntity(group);
          await client.invoke(new Api.channels.JoinChannel({ channel: entity }));
          memberCount += 1;
        }
      } catch (error) {
        const errorText = error.errorMessage || error.message || String(error);
        if (account && (errorText.includes("FLOOD") || errorText.includes("PEER_FLOOD"))) {
          this._applyFloodCooldown(account, errorText);
        }
      }
    }
  }

  async _isParticipant(client, group) {
    try {
      if (this._isInviteLink(group)) {
        const hash = this._extractInviteHash(group);
        if (!hash) return false;
        const check = await client.invoke(new Api.messages.CheckChatInvite({ hash }));
        if (check && typeof check.className === "string" && check.className.includes("ChatInviteAlready")) {
          return true;
        }
        return false;
      }
      const entity = await client.getEntity(group);
      const me = await client.getMe();
      await client.invoke(new Api.channels.GetParticipant({ channel: entity, participant: me }));
      return true;
    } catch (error) {
      return false;
    }
  }

  async _resolveGroupEntity(client, group, allowJoin, account) {
    try {
      if (this._isInviteLink(group)) {
        const hash = this._extractInviteHash(group);
        if (!hash) return { ok: false, error: "Invalid invite link" };
        try {
          const check = await client.invoke(new Api.messages.CheckChatInvite({ hash }));
          if (check && check.chat) {
            return { ok: true, entity: await this._normalizeEntity(client, check.chat) };
          }
        } catch (error) {
          // ignore and try import
        }
        if (!allowJoin) {
          return { ok: false, error: "Invite link requires auto-join" };
        }
        try {
          const imported = await client.invoke(new Api.messages.ImportChatInvite({ hash }));
          if (imported && imported.chats && imported.chats.length) {
            return { ok: true, entity: await this._normalizeEntity(client, imported.chats[0]) };
          }
        } catch (error) {
          const errorText = error.errorMessage || error.message || String(error);
          if (account && (errorText.includes("FLOOD") || errorText.includes("PEER_FLOOD"))) {
            this._applyFloodCooldown(account, errorText);
          }
          if (errorText.includes("USER_ALREADY_PARTICIPANT")) {
            try {
              const check = await client.invoke(new Api.messages.CheckChatInvite({ hash }));
              if (check && check.chat) {
                return { ok: true, entity: check.chat };
              }
            } catch (checkError) {
              // fall through
            }
            return { ok: false, error: "USER_ALREADY_PARTICIPANT" };
          }
          return { ok: false, error: errorText };
        }
        return { ok: false, error: "Unable to resolve invite link" };
      }

      const entity = await client.getEntity(group);
      return { ok: true, entity: await this._normalizeEntity(client, entity) };
    } catch (error) {
      const errorText = error.errorMessage || error.message || String(error);
      if (account && (errorText.includes("FLOOD") || errorText.includes("PEER_FLOOD"))) {
        this._applyFloodCooldown(account, errorText);
      }
      return { ok: false, error: errorText };
    }
  }

  _startMonitorPolling(client) {
    if (this.monitorTimer) clearInterval(this.monitorTimer);
    this.monitorTimer = setInterval(async () => {
      if (!this.isMonitoring()) return;
      for (const [key, state] of this.monitorState.entries()) {
        try {
          const messages = await client.getMessages(state.entity, { limit: 10 });
          for (const message of messages.reverse()) {
            if (state.lastId && message.id <= state.lastId) continue;
            state.lastId = Math.max(state.lastId || 0, message.id || 0);
            if (!message.senderId) continue;
            const senderId = message.senderId.toString();
            if (this._isOwnAccount(senderId)) continue;
            let username = "";
            let accessHash = "";
            try {
              const sender = await message.getSender();
              if (sender && sender.bot) continue;
              username = sender && sender.username ? sender.username : "";
              accessHash = sender && sender.accessHash ? sender.accessHash.toString() : "";
            } catch (error) {
              username = "";
              accessHash = "";
            }
            this.store.enqueueInvite(0, senderId, username, state.source, accessHash);
            this.lastMonitorMessageAt = new Date().toISOString();
            this.lastMonitorSource = state.source;
          }
        } catch (error) {
          // ignore polling errors
        }
      }
    }, 10000);
  }

  async _normalizeEntity(client, entity) {
    if (!entity) return entity;
    if (entity.className === "Chat" || entity.className === "Channel") {
      return entity;
    }
    if (entity.id != null) {
      return await client.getEntity(entity.id);
    }
    return entity;
  }

  _isOwnAccount(userId) {
    const accounts = this.store.listAccounts();
    return accounts.some((account) => account.user_id && account.user_id.toString() === userId);
  }

  async joinGroupsForAllAccounts(competitorGroups, ourGroup, settings) {
    const groups = Array.isArray(competitorGroups) ? competitorGroups.filter(Boolean) : [];
    for (const entry of this.clients.values()) {
      if (settings.autoJoinCompetitors) {
        await this._autoJoinGroups(entry.client, groups, true, entry.account);
      }
      if (settings.autoJoinOurGroup && ourGroup) {
        await this._autoJoinGroups(entry.client, [ourGroup], true, entry.account);
      }
    }
  }

  async joinGroupsForTask(task, competitorGroups, accountIds) {
    const accounts = Array.from(this.clients.values()).filter((entry) => accountIds.includes(entry.account.id));
    const competitorBots = Math.max(1, Number(task.max_competitor_bots || 1));
    const ourBots = Math.max(1, Number(task.max_our_bots || 1));

    const competitors = competitorGroups || [];
    let cursor = 0;
    const usedForCompetitors = new Set();
    for (const group of competitors) {
      for (let i = 0; i < competitorBots; i += 1) {
        if (!accounts.length) break;
        const entry = accounts[cursor % accounts.length];
        cursor += 1;
        usedForCompetitors.add(entry.account.id);
        if (task.auto_join_competitors) {
          await this._autoJoinGroups(entry.client, [group], true, entry.account);
        }
      }
    }

    if (task.our_group) {
      const available = accounts.filter((entry) => !usedForCompetitors.has(entry.account.id));
      const pool = available.length ? available : accounts;
      for (let i = 0; i < Math.min(ourBots, pool.length); i += 1) {
        const entry = pool[i];
        if (task.auto_join_our_group) {
          await this._autoJoinGroups(entry.client, [task.our_group], true, entry.account);
        }
      }
    }
  }

  async startTaskMonitor(task, competitorGroups, accountIds) {
    const monitorAccount = this._pickClientFromAllowed(accountIds);
    if (!monitorAccount) return { ok: false, error: "No accounts for task" };
    const groups = (competitorGroups || []).filter(Boolean);
    const resolved = [];
    const errors = [];
    for (const group of groups) {
      const result = await this._resolveGroupEntity(
        monitorAccount.client,
        group,
        Boolean(task.auto_join_competitors),
        monitorAccount.account
      );
      if (result.ok) {
        resolved.push({ entity: result.entity, source: group });
      } else {
        errors.push(`${group}: ${result.error}`);
      }
    }
    if (!resolved.length) return { ok: false, error: "No groups to monitor", errors };
    if (errors.length) {
      this.store.addAccountEvent(
        monitorAccount.account.id,
        monitorAccount.account.phone,
        "monitor_error",
        errors.join(" | ")
      );
    }

    const state = new Map();
    resolved.forEach((item) => {
      const id = item.entity && item.entity.id != null ? item.entity.id.toString() : item.source;
      state.set(id, { entity: item.entity, source: item.source, lastId: 0 });
    });

    const monitorEntry = {
      timer: null,
      accountId: monitorAccount.account.id,
      groups,
      lastMessageAt: "",
      lastSource: "",
      lastErrorAt: new Map(),
      lastSkipAt: new Map()
    };
    this.store.addAccountEvent(
      monitorAccount.account.id,
      monitorAccount.account.phone,
      "monitor_started",
      `Групп: ${resolved.length}`
    );
    const timer = setInterval(async () => {
      for (const [key, st] of state.entries()) {
        try {
          const messages = await monitorAccount.client.getMessages(st.entity, { limit: 10 });
          let totalMessages = 0;
          let enqueued = 0;
          let skipped = 0;
          for (const message of messages.reverse()) {
            totalMessages += 1;
            if (st.lastId && message.id <= st.lastId) continue;
            st.lastId = Math.max(st.lastId || 0, message.id || 0);
            const senderInfo = await this._getUserInfoFromMessage(message);
            if (!senderInfo) {
              skipped += 1;
              continue;
            }
            const { userId: senderId, username, accessHash } = senderInfo;
            if (this._isOwnAccount(senderId)) continue;
            let messageDate = new Date();
            if (message.date instanceof Date) {
              messageDate = message.date;
            } else if (typeof message.date === "number") {
              messageDate = new Date(message.date * 1000);
            }
            monitorEntry.lastMessageAt = messageDate.toISOString();
            monitorEntry.lastSource = st.source;
            if (this.store.enqueueInvite(task.id, senderId, username, st.source, accessHash)) {
              enqueued += 1;
            }
          }
          if (totalMessages > 0 && enqueued === 0 && skipped > 0) {
            const now = Date.now();
            const lastSkip = monitorEntry.lastSkipAt.get(key) || 0;
            if (now - lastSkip > 60000) {
              monitorEntry.lastSkipAt.set(key, now);
              this.store.addAccountEvent(
                monitorAccount.account.id,
                monitorAccount.account.phone,
                "monitor_skip",
                `${st.source}: сообщения есть, но пользователей нет (пропущено: ${skipped})`
              );
            }
          }
        } catch (error) {
          const now = Date.now();
          const lastError = monitorEntry.lastErrorAt.get(key) || 0;
          if (now - lastError > 60000) {
            monitorEntry.lastErrorAt.set(key, now);
            this.store.addAccountEvent(
              monitorAccount.account.id,
              monitorAccount.account.phone,
              "monitor_poll_error",
              `${st.source}: ${error.message || String(error)}`
            );
          }
        }
      }
    }, 10000);

    monitorEntry.timer = timer;
    this.taskMonitors.set(task.id, monitorEntry);
    return { ok: true, errors };
  }

  async parseHistoryForTask(task, competitorGroups, accountIds) {
    const groups = (competitorGroups || []).filter(Boolean);
    if (!groups.length) return { ok: false, error: "No competitor groups" };
    const entry = this._pickClientFromAllowed(accountIds);
    if (!entry) return { ok: false, error: "No available accounts" };
    const perGroupLimit = Math.max(1, Number(task.history_limit || 200));

    if (task.auto_join_competitors) {
      await this._autoJoinGroups(entry.client, groups, true, entry.account);
    }
    const summaryLines = [];
    let totalEnqueued = 0;
    const errors = [];
    for (const group of groups) {
      const resolved = await this._resolveGroupEntity(entry.client, group, Boolean(task.auto_join_competitors), entry.account);
      if (!resolved.ok) {
        errors.push(`${group}: ${resolved.error}`);
        continue;
      }
      const messages = await entry.client.getMessages(resolved.entity, { limit: perGroupLimit });
      let total = 0;
      let enqueued = 0;
      let skipped = 0;
      for (const message of messages) {
        total += 1;
        const senderInfo = await this._getUserInfoFromMessage(message);
        if (!senderInfo) {
          skipped += 1;
          continue;
        }
        const { userId: senderId, username, accessHash } = senderInfo;
        if (this._isOwnAccount(senderId)) continue;
        if (this.store.enqueueInvite(task.id, senderId, username, group, accessHash)) {
          enqueued += 1;
          totalEnqueued += 1;
        }
      }
      summaryLines.push(`${group}: сообщений ${total}, добавлено ${enqueued}, пропущено ${skipped}`);
    }
    if (summaryLines.length) {
      this.store.addAccountEvent(
        entry.account.id,
        entry.account.phone,
        "history_summary",
        summaryLines.join(" | ")
      );
    }
    if (totalEnqueued === 0 && errors.length === 0) {
      this.store.addAccountEvent(
        entry.account.id,
        entry.account.phone,
        "history_empty",
        "История собрана, но пользователей для очереди нет"
      );
    }
    return { ok: true, errors };
  }

  async _getUserInfoFromMessage(message) {
    try {
      const sender = await message.getSender();
      if (!sender || sender.className !== "User") return null;
      if (sender.bot) return null;
      const userId = sender.id != null ? sender.id.toString() : "";
      if (!userId) return null;
      const username = sender.username ? sender.username : "";
      const accessHash = sender.accessHash ? sender.accessHash.toString() : "";
      return { userId, username, accessHash };
    } catch (error) {
      return null;
    }
  }

  stopTaskMonitor(taskId) {
    const entry = this.taskMonitors.get(taskId);
    if (!entry) return;
    clearInterval(entry.timer);
    this.taskMonitors.delete(taskId);
  }

  getTaskMonitorInfo(taskId) {
    const entry = this.taskMonitors.get(taskId);
    if (!entry) {
      return { monitoring: false, accountId: 0, groups: [], lastMessageAt: "", lastSource: "" };
    }
    return {
      monitoring: true,
      accountId: entry.accountId || 0,
      groups: entry.groups || [],
      lastMessageAt: entry.lastMessageAt || "",
      lastSource: entry.lastSource || ""
    };
  }

  _isInCooldown(account) {
    if (!account || !account.cooldown_until) return false;
    try {
      return new Date(account.cooldown_until).getTime() > Date.now();
    } catch (error) {
      return false;
    }
  }

  _applyFloodCooldown(account, reason) {
    const settings = this.store.getSettings();
    const minutes = Number(settings.floodCooldownMinutes || 1440);
    this.store.setAccountCooldown(account.id, minutes, reason);
    this.store.addAccountEvent(account.id, account.phone || "", "flood", `FLOOD cooldown: ${minutes} min. ${reason || ""}`);
    account.status = "limited";
    account.last_error = reason || "";
    account.cooldown_until = minutes > 0 ? new Date(Date.now() + minutes * 60000).toISOString() : "";
    account.cooldown_reason = reason || "";
  }

  _isInviteLink(value) {
    return value.includes("joinchat/") || value.includes("t.me/+");
  }

  _extractInviteHash(value) {
    if (value.includes("joinchat/")) {
      return value.split("joinchat/")[1].split(/[?#]/)[0];
    }
    if (value.includes("t.me/+")) {
      return value.split("t.me/+")[1].split(/[?#]/)[0];
    }
    return "";
  }
}

module.exports = { TelegramManager };
