// background/storage.js — abstracción sobre chrome.storage.local
// v0.2: añadidas etiquetas, plantillas, pipeline, bienvenida, alertas, configuración

import { aggregateMemberComments } from './parsers.js';

const ROOT_KEY = 'skoop_v1';

async function _load() {
  const result = await chrome.storage.local.get(ROOT_KEY);
  return result[ROOT_KEY] || { communities: {}, settings: {} };
}

async function _save(root) {
  await chrome.storage.local.set({ [ROOT_KEY]: root });
}

// ── CANDADO DE ESCRITURA ──────────────────────────────────
// Serializa TODA operación read-modify-write sobre ROOT_KEY. Durante un scan o
// la navegación normal hay muchas escrituras concurrentes (lote de miembros,
// estado del scan, endpoints, meta de API) sobre la MISMA clave de storage.
// Sin esta cola, dos operaciones leen el mismo blob y la última en escribir
// pisa a la otra (lost-update) — por eso el scan contaba 37 pero guardaba 0.
let _writeChain = Promise.resolve();

function _mutate(fn) {
  const run = _writeChain.then(async () => {
    const root = await _load();
    const result = await fn(root);
    await _save(root);
    return result;
  });
  // La cadena nunca debe romperse aunque una operación falle.
  _writeChain = run.then(() => {}, () => {});
  return run;
}

// ── COMUNIDADES ───────────────────────────────────────────

export async function listCommunities() {
  const root = await _load();
  return Object.values(root.communities).map((c) => ({
    slug: c.slug,
    name: c.meta?.name || c.slug,
    memberCount: Object.keys(c.members || {}).length,
    postCount: Object.keys(c.posts || {}).length,
    lastSync: c.lastSync || null,
  }));
}

export async function getCommunityRaw(slug) {
  const root = await _load();
  return root.communities[slug] || null;
}

export async function saveCommunityMeta(slug, meta) {
  return _mutate((root) => {
    _ensureCommunity(root, slug);
    root.communities[slug].meta = { ...root.communities[slug].meta, ...meta };
  });
}

export async function getCommunityStats(slug) {
  const root = await _load();
  const c = root.communities[slug];
  if (!c) return null;

  const members = Object.values(c.members || {});
  const posts = Object.values(c.posts || {});
  const now = Date.now();

  const stats = {
    slug,
    name: c.meta?.name || slug,
    totalMembers: members.length,
    totalPosts: posts.length,
    lastSync: c.lastSync || null,
    riskBuckets: { active: 0, silent: 0, atRisk: 0, churned: 0, unknown: 0 },
    endpointCount: Object.keys(c.endpoints || {}).length,
  };

  for (const m of members) {
    const bucket = _riskBucket(m, now);
    stats.riskBuckets[bucket]++;
  }

  return stats;
}

// ── MIEMBROS ──────────────────────────────────────────────

export async function saveMembers(slug, members) {
  return _mutate((root) => {
    _ensureCommunity(root, slug);
    const store = root.communities[slug].members;
    const now = Date.now();

    for (const m of members) {
      const id = m.id || m.userId || m.uuid || m.username;
      if (!id) continue;
      store[id] = {
        ...store[id],
        ...m,
        id,
        firstSeen: store[id]?.firstSeen || now,
        lastIndexed: now,
      };
    }

    root.communities[slug].lastSync = now;
  });
}

export async function saveMember(slug, member) {
  await saveMembers(slug, [member]);
}

export async function getMembers(slug, riskFilter) {
  const root = await _load();
  const c = root.communities[slug];
  if (!c) return [];

  const now = Date.now();
  const rawMembers = Object.values(c.members || {});
  // Fase 3: actividad de comentarios por miembro (deducida de los comentarios capturados)
  const activity = aggregateMemberComments(rawMembers, Object.values(c.comments || {}));
  const members = rawMembers.map((m) => {
    const act = activity[m.id] || {};
    return {
      ...m,
      riskLevel: _riskBucket(m, now),
      daysSinceLastPost: m.lastPostTs ? Math.floor((now - m.lastPostTs) / 86400000) : null,
      monthsInCommunity: _monthsSince(m.joinedAt, now),
      daysUntilCancel: (m.cancelAt || m.accessUntil) ? Math.ceil(((m.cancelAt || m.accessUntil) - now) / 86400000) : null,
      commentCount: act.commentCount ?? m.commentCount,
      lastCommentAt: act.lastCommentAt ?? null,
      lastCommentText: act.lastCommentText ?? '',
      daysSinceLastComment: act.lastCommentAt ? Math.floor((now - act.lastCommentAt) / 86400000) : null,
    };
  });

  if (riskFilter) return members.filter((m) => m.riskLevel === riskFilter);
  return members;
}

// Fase 2: miembros "por irse" — cancelaron pero su acceso sigue activo.
// Devuelve los marcados como 'cancelling' (o 'canceled' reciente), ordenados
// por días restantes ascendente (los que se van primero, arriba).
export async function getCancellingMembers(slug) {
  const root = await _load();
  const c = root.communities[slug];
  if (!c) return [];
  const now = Date.now();
  return Object.values(c.members || {})
    .filter((m) => m.subStatus === 'cancelling' || m.subStatus === 'canceled')
    .map((m) => ({
      ...m,
      riskLevel: _riskBucket(m, now),
      daysSinceLastPost: m.lastPostTs ? Math.floor((now - m.lastPostTs) / 86400000) : null,
      monthsInCommunity: _monthsSince(m.joinedAt, now),
      daysUntilCancel: (m.cancelAt || m.accessUntil) ? Math.ceil(((m.cancelAt || m.accessUntil) - now) / 86400000) : null,
    }))
    .sort((a, b) => {
      // 'cancelling' (aún con acceso) antes que 'canceled'; luego por días restantes
      if (a.subStatus !== b.subStatus) return a.subStatus === 'cancelling' ? -1 : 1;
      return (a.daysUntilCancel ?? 9999) - (b.daysUntilCancel ?? 9999);
    });
}

// Fase 2: marca como 'cancelling' EXACTAMENTE a los miembros que están en la
// pestaña "Cancelling" de Skool (fuente de verdad, vía ?t=cancelling). Limpia
// el flag de quien ya no aparece (re-suscribió o ya churneó). Determinista —
// no depende de adivinar el nombre del campo de cancelación en la metadata.
export async function setCancellingMembers(slug, cancellingIds) {
  return _mutate((root) => {
    const c = root.communities[slug];
    if (!c || !c.members) return;
    const set = new Set((cancellingIds || []).map(String));
    for (const [id, m] of Object.entries(c.members)) {
      if (set.has(String(id))) {
        m.subStatus = 'cancelling';
      } else if (m.subStatus === 'cancelling') {
        m.subStatus = null;
        m.cancelAt = null;
      }
    }
    c.cancellingSyncedAt = Date.now();
  });
}

// ── POSTS ─────────────────────────────────────────────────

export async function savePosts(slug, posts) {
  return _mutate((root) => {
    _ensureCommunity(root, slug);
    const store = root.communities[slug].posts;
    const now = Date.now();

    for (const p of posts) {
      const id = p.id || p.postId || p.uuid;
      if (!id) continue;

      // Actualizar lastPostTs del autor si existe
      if (p.authorId && root.communities[slug].members[p.authorId]) {
        const existing = root.communities[slug].members[p.authorId].lastPostTs || 0;
        const postTs = p.createdAt ? new Date(p.createdAt).getTime() : now;
        if (postTs > existing) {
          root.communities[slug].members[p.authorId].lastPostTs = postTs;
        }
      }

      store[id] = { ...store[id], ...p, id, indexedAt: now };
    }
  });
}

export async function getFeed(slug, limit = 50) {
  const root = await _load();
  const c = root.communities[slug];
  if (!c) return [];

  return Object.values(c.posts || {})
    .sort((a, b) => {
      const ta = a.createdAt ? new Date(a.createdAt).getTime() : 0;
      const tb = b.createdAt ? new Date(b.createdAt).getTime() : 0;
      return tb - ta;
    })
    .slice(0, limit);
}

// ── COMENTARIOS ───────────────────────────────────────────

export async function saveComments(slug, comments, postId) {
  return _mutate((root) => {
    _ensureCommunity(root, slug);

    if (!root.communities[slug].comments) {
      root.communities[slug].comments = {};
    }

    const store = root.communities[slug].comments;
    const now = Date.now();

    for (const c of comments) {
      const id = c.id || c.commentId || c.uuid;
      if (!id) continue;

      // Actualizar lastPostTs del autor (comentar = actividad)
      if (c.authorId && root.communities[slug].members[c.authorId]) {
        const existing = root.communities[slug].members[c.authorId].lastPostTs || 0;
        const commentTs = c.createdAt ? new Date(c.createdAt).getTime() : now;
        if (commentTs > existing) {
          root.communities[slug].members[c.authorId].lastPostTs = commentTs;
        }
      }

      store[id] = { ...store[id], ...c, id, postId, indexedAt: now };
    }
  });
}

// ── ENDPOINTS DESCUBIERTOS ────────────────────────────────

export async function recordEndpoint(slug, info) {
  return _mutate((root) => {
    _ensureCommunity(root, slug);

    const key = `${info.method}:${info.url}`;
    const existing = root.communities[slug].endpoints[key];

    root.communities[slug].endpoints[key] = {
      ...info,
      count: (existing?.count || 0) + 1,
      lastSeen: Date.now(),
      firstSeen: existing?.firstSeen || Date.now(),
    };
  });
}

export async function getDiscoveredEndpoints(slug) {
  const root = await _load();
  const c = root.communities[slug];
  if (!c) return [];
  return Object.values(c.endpoints || {}).sort((a, b) => b.count - a.count);
}

// ── UTILIDADES ────────────────────────────────────────────

export async function clearCommunity(slug) {
  return _mutate((root) => {
    delete root.communities[slug];
  });
}

export async function clearAllData() {
  // Preserve license and settings — only wipe community data
  const preserved = await chrome.storage.local.get(['skoop_license', SETTINGS_KEY, ALERTS_KEY]);
  await chrome.storage.local.clear();
  if (Object.keys(preserved).length > 0) {
    await chrome.storage.local.set(preserved);
  }
}

// ── ETIQUETAS (tipos globales) ────────────────────────────

const LABEL_TYPES_KEY = 'skoop_label_types';

export async function getLabelTypes() {
  const r = await chrome.storage.local.get(LABEL_TYPES_KEY);
  return r[LABEL_TYPES_KEY] || [];
}

export async function saveLabelType(label) {
  const list = await getLabelTypes();
  const idx = list.findIndex((l) => l.id === label.id);
  if (idx >= 0) list[idx] = label;
  else list.push(label);
  await chrome.storage.local.set({ [LABEL_TYPES_KEY]: list });
}

export async function deleteLabelType(id) {
  const list = await getLabelTypes();
  await chrome.storage.local.set({ [LABEL_TYPES_KEY]: list.filter((l) => l.id !== id) });
}

// Asigna/quita una etiqueta de un miembro (toggle)
export async function toggleMemberLabel(slug, memberId, labelId) {
  return _mutate((root) => {
    const m = root.communities[slug]?.members?.[String(memberId)];
    if (!m) return;
    const ids = m.labelIds || [];
    const idx = ids.indexOf(labelId);
    if (idx >= 0) ids.splice(idx, 1);
    else ids.push(labelId);
    m.labelIds = ids;
  });
}

// ── PIPELINE ─────────────────────────────────────────────

export async function saveMemberPipeline(slug, memberId, stage, notes) {
  return _mutate((root) => {
    _ensureCommunity(root, slug);
    const m = root.communities[slug].members[String(memberId)];
    if (!m) return;
    m.pipeline = { stage, notes: notes || '', updatedAt: Date.now() };
  });
}

export async function getPipelineMembers(slug) {
  const root = await _load();
  const c = root.communities[slug];
  if (!c) return [];
  const now = Date.now();
  return Object.values(c.members || {})
    .filter((m) => m.pipeline?.stage && m.pipeline.stage !== 'none')
    .map((m) => ({
      ...m,
      riskLevel: _riskBucket(m, now),
      daysSinceLastPost: m.lastPostTs ? Math.floor((now - m.lastPostTs) / 86400000) : null,
      monthsInCommunity: _monthsSince(m.joinedAt, now),
    }))
    .sort((a, b) => (b.pipeline?.updatedAt || 0) - (a.pipeline?.updatedAt || 0));
}

// ── PLANTILLAS ────────────────────────────────────────────

const TEMPLATES_KEY = 'skoop_templates';

export async function getTemplates() {
  const r = await chrome.storage.local.get(TEMPLATES_KEY);
  return r[TEMPLATES_KEY] || [];
}

export async function saveTemplate(tpl) {
  const list = await getTemplates();
  const idx = list.findIndex((t) => t.id === tpl.id);
  if (idx >= 0) list[idx] = tpl;
  else list.push(tpl);
  await chrome.storage.local.set({ [TEMPLATES_KEY]: list });
}

export async function deleteTemplate(id) {
  const list = await getTemplates();
  await chrome.storage.local.set({ [TEMPLATES_KEY]: list.filter((t) => t.id !== id) });
}

// ── BIENVENIDA AUTOMÁTICA ─────────────────────────────────

const WELCOME_KEY = 'skoop_welcome';

export async function getWelcomeConfig() {
  const r = await chrome.storage.local.get(WELCOME_KEY);
  return r[WELCOME_KEY] || { enabled: false, templateId: null, sentTo: [] };
}

export async function saveWelcomeConfig(config) {
  const existing = await getWelcomeConfig();
  await chrome.storage.local.set({ [WELCOME_KEY]: { ...existing, ...config } });
}

export async function markWelcomeSent(memberId) {
  const c = await getWelcomeConfig();
  if (!c.sentTo.includes(String(memberId))) {
    c.sentTo.push(String(memberId));
    await chrome.storage.local.set({ [WELCOME_KEY]: c });
  }
}

// Retorna miembros nuevos que aún no recibieron bienvenida
export async function getPendingWelcomes(slug) {
  const [root, config, templates] = await Promise.all([
    _load(),
    getWelcomeConfig(),
    getTemplates(),
  ]);
  if (!config.enabled || !config.templateId) return [];
  const tpl = templates.find((t) => t.id === config.templateId);
  if (!tpl) return [];
  const c = root.communities[slug];
  if (!c) return [];
  const now = Date.now();
  return Object.values(c.members || {})
    .filter((m) => m.id && !config.sentTo.includes(String(m.id)))
    .map((m) => ({ member: m, template: tpl }))
    .slice(0, 50);
}

// ── ALERTAS ───────────────────────────────────────────────

const ALERTS_KEY = 'skoop_alerts';

export async function getAlertsConfig() {
  const r = await chrome.storage.local.get(ALERTS_KEY);
  return r[ALERTS_KEY] || { churnAlert: true, newMemberAlert: true };
}

export async function saveAlertsConfig(config) {
  await chrome.storage.local.set({ [ALERTS_KEY]: config });
}

// ── CONFIGURACIÓN GENERAL ─────────────────────────────────

const SETTINGS_KEY = 'skoop_settings';

export async function getSettings() {
  const r = await chrome.storage.local.get(SETTINGS_KEY);
  return r[SETTINGS_KEY] || { blurMode: false, darkMode: false };
}

export async function saveSettings(settings) {
  const existing = await getSettings();
  await chrome.storage.local.set({ [SETTINGS_KEY]: { ...existing, ...settings } });
}

// ── NUEVOS MIEMBROS ───────────────────────────────────────

export async function getNewMembers(slug, sinceMs) {
  const root = await _load();
  const c = root.communities[slug];
  if (!c) return [];
  const now = Date.now();
  return Object.values(c.members || {})
    .filter((m) => m.firstSeen && m.firstSeen >= sinceMs)
    .map((m) => ({ ...m, riskLevel: _riskBucket(m, now), monthsInCommunity: _monthsSince(m.joinedAt, now) }));
}

// ── DOM SCAN ──────────────────────────────────────────────

export async function saveDomScanBatch(slug, domMembers, totalSoFar) {
  return _mutate((root) => {
    _ensureCommunity(root, slug);
    const store = root.communities[slug].members;
    const now = Date.now();

    for (const m of domMembers) {
      const id = m.id || m.username;
      if (!id) continue;

      const existing = store[id] || {};

      // Preservar todos los campos que ya teníamos de la API
      const preserved = { ...existing };

      // DOM aporta: lastSeenTs (muy confiable), avatar (si falta), username
      store[id] = {
        ...preserved,
        id,
        username: preserved.username || m.username,
        name: preserved.name || m.name,           // API name tiene prioridad
        avatarUrl: preserved.avatarUrl || m.avatarUrl,
        // Usar el timestamp más reciente entre API y DOM
        lastPostTs: _bestTs(preserved.lastPostTs, m.lastSeenTs),
        lastSeenAt: m.lastSeenTs
          ? new Date(m.lastSeenTs).toISOString()
          : preserved.lastSeenAt,
        firstSeen: preserved.firstSeen || now,
        lastIndexed: now,
      };
    }

    root.communities[slug].lastSync = now;
    if (!root.communities[slug].scanStatus) root.communities[slug].scanStatus = {};
    root.communities[slug].scanStatus.scanned = totalSoFar;
    root.communities[slug].scanStatus.running = true;
  });
}

export async function saveScanStatus(slug, status) {
  return _mutate((root) => {
    _ensureCommunity(root, slug);
    root.communities[slug].scanStatus = {
      ...(root.communities[slug].scanStatus || {}),
      ...status,
      updatedAt: Date.now(),
    };
  });
}

export async function getScanStatus(slug) {
  const root = await _load();
  return root.communities[slug]?.scanStatus || { scanned: 0, running: false };
}

// Meses (enteros) desde la fecha de ingreso. Null si no hay fecha válida.
function _monthsSince(joinedAt, now) {
  if (!joinedAt) return null;
  const t = new Date(joinedAt).getTime();
  if (Number.isNaN(t)) return null;
  return Math.max(0, Math.floor((now - t) / (30.44 * 86400000)));
}

function _bestTs(a, b) {
  if (!a && !b) return null;
  if (!a) return b;
  if (!b) return a;
  return Math.max(a, b);
}

function _ensureCommunity(root, slug) {
  if (!root.communities[slug]) {
    root.communities[slug] = {
      slug,
      meta: {},
      members: {},
      posts: {},
      comments: {},
      endpoints: {},
      lastSync: null,
    };
  }
}

// Retorna el bucket de riesgo según los días de inactividad.
// NUNCA usar firstSeen como proxy de actividad — esa es nuestra timestamp de indexación, no la del miembro.
function _riskBucket(member, now) {
  const lastActivity = member.lastPostTs || (member.lastSeenAt ? new Date(member.lastSeenAt).getTime() : 0);
  if (!lastActivity) return 'unknown';
  const daysSince = (now - lastActivity) / 86400000;
  if (daysSince <= 7) return 'active';
  if (daysSince <= 30) return 'silent';
  if (daysSince <= 60) return 'atRisk';
  return 'churned';
}
