import { getDb } from '../db/locator';

type GroupStatus = 'pending' | 'allowed' | 'blocked';

type CacheEntry = {
  status: GroupStatus;
  label: string | null;
};

export class AllowedGroups {

  // Caché en memoria: group_id (JID completo) -> { status, label }
  private static cache = new Map<string, CacheEntry>();

  private static nowExpr = "strftime('%Y-%m-%d %H:%M:%f','now')";

  static clearCache() {
    this.cache.clear();
  }

  static resetForTests() {
    this.clearCache();
  }

  private static getRow(groupId: string): { group_id: string; label: string | null; status: GroupStatus } | null {
    try {
      const row = getDb()
        .prepare(`SELECT group_id, label, status FROM allowed_groups WHERE group_id = ?`)
        .get(groupId) as { group_id?: string; label?: string | null; status?: string } | undefined;
      if (!row) return null;
      return {
        group_id: String(row.group_id),
        label: row.label != null ? String(row.label) : null,
        status: String(row.status) as GroupStatus,
      };
    } catch {
      // Tabla podría no existir en contextos muy tempranos: degradar a null
      return null;
    }
  }

  static isAllowed(groupId: string | null | undefined): boolean {
    const gid = String(groupId || '').trim();
    if (!gid) return false;

    const cached = this.cache.get(gid);
    if (cached) return cached.status === 'allowed';

    const row = this.getRow(gid);
    const status = row?.status || 'pending';
    const label = row?.label ?? null;
    this.cache.set(gid, { status, label });
    return status === 'allowed';
  }

  /**
   * Inserta un grupo como pending si no existe. No degrada estados existentes (allowed/blocked).
   * Actualiza label si se proporciona y cambió.
   */
  static upsertPending(groupId: string, label?: string | null, discoveredBy?: string | null): void {
    const gid = String(groupId || '').trim();
    if (!gid) return;

    const row = this.getRow(gid);
    if (!row) {
      // Insertar como pending
      getDb()
        .prepare(`
          INSERT INTO allowed_groups (group_id, label, status, discovered_at, updated_at, discovered_by)
          VALUES (?, ?, 'pending', ${this.nowExpr}, ${this.nowExpr}, ?)
        `)
        .run(gid, label ?? null, discoveredBy ?? null);
      this.cache.set(gid, { status: 'pending', label: label ?? null });
      return;
    }

    // No cambiar status existente. Solo actualizar label si se aporta y cambió.
    const newLabel = label ?? row.label;
    if (label != null && String(row.label ?? '') !== String(label)) {
      getDb()
        .prepare(`
          UPDATE allowed_groups
          SET label = ?, updated_at = ${this.nowExpr}
          WHERE group_id = ?
        `)
        .run(newLabel, gid);
    }
    this.cache.set(gid, { status: row.status, label: newLabel ?? null });
  }

  /**
   * Establece el estado de un grupo (upsert). Devuelve true si cambió algo (estado o label).
   */
  static setStatus(groupId: string, status: GroupStatus, label?: string | null): boolean {
    const gid = String(groupId || '').trim();
    if (!gid) return false;

    const before = this.getRow(gid);
    getDb()
      .prepare(`
        INSERT INTO allowed_groups (group_id, label, status, discovered_at, updated_at)
        VALUES (?, ?, ?, ${this.nowExpr}, ${this.nowExpr})
        ON CONFLICT(group_id) DO UPDATE SET
          status = excluded.status,
          label = COALESCE(excluded.label, allowed_groups.label),
          updated_at = excluded.updated_at
      `)
      .run(gid, label ?? null, status);

    const after = this.getRow(gid) || { group_id: gid, label: label ?? null, status };
    this.cache.set(gid, { status: after.status, label: after.label });

    return (
      !before ||
      before.status !== after.status ||
      (label != null && String(before.label ?? '') !== String(label))
    );
  }

  static listByStatus(status: GroupStatus): Array<{ group_id: string; label: string | null }> {
    const rows = getDb()
      .prepare(
        `SELECT group_id, label FROM allowed_groups WHERE status = ? ORDER BY group_id`
      )
      .all(status) as Array<{ group_id: string; label: string | null }>;
    return rows.map(r => ({
      group_id: String(r.group_id),
      label: r.label != null ? String(r.label) : null,
    }));
  }

  /**
   * Marca como allowed todos los group_ids provistos en una cadena separada por comas.
   * Si env no se pasa, usa process.env.ALLOWED_GROUPS.
   */
  static seedFromEnv(env?: string | null): void {
    const val = (env ?? process?.env?.ALLOWED_GROUPS ?? '').trim();
    if (!val) return;
    const ids = val
      .split(',')
      .map(s => s.trim())
      .filter(Boolean);
    for (const gid of ids) {
      this.setStatus(gid, 'allowed', null);
    }
  }
}
