// File-based JSON store for Weavy accounts.
// Each account: { id, label, token, recipeId, addedAt, lastUsedAt, runs, maxRuns, credits, expired, archived }

import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const STORE_PATH = path.join(__dirname, "..", "data", "accounts.json");
const DELETED_PATH = path.join(__dirname, "..", "data", "deleted-emails.json");
const MONTHLY_RESET_PATH = path.join(__dirname, "..", "data", "monthly-reset.json");

// ─── Pool Account Limits ──────────────────────────────────────────────────────
export let DEFAULT_MAX_RUNS = 1;       // Default max runs per account per month (150 credits / 80 cost = 1 run)
export let DEFAULT_MONTHLY_CREDITS = 150; // Credits reset each month
export const MONTHLY_RUN_LIMIT = 15;   // Legacy compat — kept for existing flow

// ─── Blacklist email terhapus (agar tidak di-import ulang oleh sync) ──────────
function readDeletedEmails() {
  try {
    if (fs.existsSync(DELETED_PATH)) return JSON.parse(fs.readFileSync(DELETED_PATH, "utf-8"));
  } catch {}
  return [];
}

function writeDeletedEmails(list) {
  ensureDir();
  fs.writeFileSync(DELETED_PATH, JSON.stringify(list, null, 2));
}

function addDeletedEmail(email) {
  if (!email) return;
  const list = readDeletedEmails();
  if (!list.includes(email)) {
    list.push(email);
    writeDeletedEmails(list);
    console.log(`[Blacklist] Email ${email} ditambahkan ke blacklist hapus.`);
  }
}

export function isEmailBlacklisted(email) {
  return readDeletedEmails().includes(email);
}

export function getDeletedEmails() {
  return readDeletedEmails();
}

export function clearDeletedEmails() {
  writeDeletedEmails([]);
}

function ensureDir() {
  const dir = path.dirname(STORE_PATH);
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
}

export function getCurrentBillingMonth(date = new Date()) {
  const d = new Date(date);
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`;
}

function getBillingMonthFromValue(value) {
  if (!value) return null;
  const d = new Date(value);
  if (Number.isNaN(d.getTime())) return null;
  return getCurrentBillingMonth(d);
}

function inferLimitMonth(account, currentMonth) {
  return (
    account.archivedMonth ||
    getBillingMonthFromValue(account.archivedAt) ||
    getBillingMonthFromValue(account.lastUsedAt) ||
    getBillingMonthFromValue(account.updatedAt) ||
    currentMonth
  );
}

function normalizeAccountState(account, currentMonth = getCurrentBillingMonth()) {
  const normalized = { ...account };
  let changed = false;

  const maxRuns = typeof normalized.maxRuns === "number" && normalized.maxRuns > 0
    ? normalized.maxRuns
    : DEFAULT_MAX_RUNS;

  if (normalized.runs == null || Number.isNaN(Number(normalized.runs))) {
    normalized.runs = 0;
    changed = true;
  } else {
    const cleanRuns = Math.max(0, Math.round(Number(normalized.runs)));
    if (cleanRuns !== normalized.runs) {
      normalized.runs = cleanRuns;
      changed = true;
    }
  }

  // Normalize credits — default 150 for pool accounts
  if (normalized.credits == null || Number.isNaN(Number(normalized.credits))) {
    normalized.credits = DEFAULT_MONTHLY_CREDITS;
    changed = true;
  }

  // Auto-archive when runs >= maxRuns (pool account limit reached)
  // Goes to Dashboard Archive tab, auto-restores after 31 days with runs → 0
  if (!normalized.archived && (normalized.runs || 0) >= maxRuns) {
    normalized.archived = true;
    normalized.archivedAt = normalized.archivedAt || new Date().toISOString();
    normalized.archivedMonth = normalized.archivedMonth || currentMonth;
    normalized.archiveReason = normalized.archiveReason || "runs_limit";
    changed = true;
  }

  // Auto-restore archived accounts after 31 days (runs_limit + legacy monthly_limit)
  // KECUALI akun yang diblokir (figmaBlocked / googleBlocked) — jangan di-restore otomatis
  if (normalized.archived && !normalized.figmaBlocked && !normalized.googleBlocked) {
    const archivedTime = normalized.archivedAt ? new Date(normalized.archivedAt).getTime() : 0;
    const thirtyOneDaysMs = 31 * 24 * 60 * 60 * 1000;
    const isPastThirtyOneDays = (Date.now() - archivedTime) >= thirtyOneDaysMs;

    // Jangan restore kalau baru di-archive (< 1 menit) — cegah immediate restore cycle
    const justArchived = (Date.now() - archivedTime) < 60000;

    const shouldRestore =
      !justArchived && (
        (normalized.archiveReason === "runs_limit" && isPastThirtyOneDays) ||
        (normalized.archiveReason === "monthly_limit" && isPastThirtyOneDays)
      );

    // Also restore on month change (new billing month)
    const newMonth = (
      !normalized.archivedMonth ||
      normalized.archivedMonth !== currentMonth
    );
    const shouldRestoreOnNewMonth =
      (normalized.archiveReason === "monthly_limit" || normalized.archiveReason === "runs_limit") &&
      newMonth && (normalized.runs || 0) > 0;

    if (shouldRestore || shouldRestoreOnNewMonth) {
      normalized.archived = false;
      normalized.archivedAt = null;
      normalized.archivedMonth = null;
      normalized.archiveReason = null;
      normalized.restoredAt = new Date().toISOString();
      normalized.runs = 0;
      normalized.expired = false;
      changed = true;
    }
  }

  // Normalize updatedAt
  if (!normalized.updatedAt) {
    normalized.updatedAt = normalized.addedAt || new Date().toISOString();
    changed = true;
  }

  return { account: normalized, changed };
}

function normalizeAccountList(list) {
  const currentMonth = getCurrentBillingMonth();
  let changed = false;
  const normalized = list.map((account) => {
    const result = normalizeAccountState(account, currentMonth);
    if (result.changed) changed = true;
    return result.account;
  });
  return { accounts: normalized, changed };
}

function readAll() {
  ensureDir();
  if (!fs.existsSync(STORE_PATH)) return [];
  try {
    const raw = JSON.parse(fs.readFileSync(STORE_PATH, "utf-8"));
    const { accounts, changed } = normalizeAccountList(Array.isArray(raw) ? raw : []);
    if (changed) {
      writeAll(accounts);
    }
    return accounts;
  } catch {
    return [];
  }
}

function writeAll(list) {
  ensureDir();
  fs.writeFileSync(STORE_PATH, JSON.stringify(list, null, 2));
}

export function decodeJwt(token) {
  try {
    const [, payload] = token.split(".");
    const json = Buffer.from(payload, "base64").toString("utf-8");
    return JSON.parse(json);
  } catch {
    return null;
  }
}

export function isExpired(token) {
  const d = decodeJwt(token);
  if (!d?.exp) return false;
  return Date.now() / 1000 > d.exp;
}

export function listAccounts() {
  return readAll();
}

export function listAccountsSafe({ archived = false, includeAll = false } = {}) {
  return readAll()
    .filter((a) => includeAll || Boolean(a.archived) === Boolean(archived))
    .map((a) => {
    const decoded = decodeJwt(a.token);
    const jwtExpired = isExpired(a.token);
    return {
      id: a.id,
      label: a.label,
      recipeId: a.recipeId,
      addedAt: a.addedAt,
      lastUsedAt: a.lastUsedAt,
      runs: a.runs || 0,
      maxRuns: a.maxRuns || DEFAULT_MAX_RUNS,
      credits: a.credits || DEFAULT_MONTHLY_CREDITS,
      email: decoded?.email,
      exp: decoded?.exp,
      expired: jwtExpired || Boolean(a.expired),
      expiredAt: a.expiredAt || null,
      expiredMonth: a.expiredMonth || null,
      expireReason: a.expireReason || null,
      archived: Boolean(a.archived),
      archivedAt: a.archivedAt || null,
      archivedMonth: a.archivedMonth || null,
      archiveReason: a.archiveReason || null,
      restoredAt: a.restoredAt || null,
      googleBlocked: Boolean(a.googleBlocked),
      figmaBlocked: Boolean(a.figmaBlocked),
      password: Boolean(a.password), // hanya boolean — tidak expose password asli
      tokenPreview: a.token.slice(0, 24) + "..." + a.token.slice(-12),
    };
  });
}

export function listArchivedAccountsSafe() {
  return listAccountsSafe({ archived: true });
}

export function getAccount(id) {
  return readAll().find((a) => a.id === id) || null;
}

export async function pushToRemoteServer(acc) {
  const remoteUrl = process.env.REMOTE_SERVER_URL;
  const ownerPass = process.env.OWNER_PASSWORD || "4rg0v14N";
  const adminPass = process.env.REMOTE_ADMIN_PASSWORD || process.env.ADMIN_PASSWORD || "";
  // Prioritas: OWNER password (selalu lolos requireOwner), fallback ke REMOTE_ADMIN
  const remotePass = ownerPass || adminPass;

  if (!remoteUrl) return;

  const url = remoteUrl.replace(/\/$/, "");
  console.log(`[Sync] Mengirim akun ${acc.email} ke remote server di ${url}...`);

  try {
    if (typeof fetch === "undefined") {
      console.warn("[Sync] Global fetch tidak tersedia di versi Node ini. Gagal push.");
      return;
    }
    const res = await fetch(`${url}/api/admin/accounts`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-admin-password": remotePass,
      },
      body: JSON.stringify({
        token: acc.token,
        recipeId: acc.recipeId,
        label: acc.label,
        email: acc.email,
        password: acc.password,
        runs: acc.runs || 0,
        maxRuns: acc.maxRuns,
        credits: acc.credits,
        expired: Boolean(acc.expired),
        archived: Boolean(acc.archived),
        archivedAt: acc.archivedAt || null,
        archivedMonth: acc.archivedMonth || null,
        archiveReason: acc.archiveReason || null,
        restoredAt: acc.restoredAt || null,
        googleBlocked: Boolean(acc.googleBlocked),
        googleBlockedAt: acc.googleBlockedAt || null,
        googleBlockedReason: acc.googleBlockedReason || null,
        figmaBlocked: Boolean(acc.figmaBlocked),
        figmaBlockedAt: acc.figmaBlockedAt || null,
        figmaBlockedReason: acc.figmaBlockedReason || null,
        updatedAt: acc.updatedAt || null,
      }),
    });

    if (res.ok) {
      console.log(`[Sync] ✅ Akun ${acc.email} berhasil disinkronisasi ke remote server.`);
    } else {
      const data = await res.json().catch(() => ({}));
      const errMsg = data.error || `HTTP ${res.status}`;
      console.error(`[Sync] ❌ Gagal sinkronisasi: ${errMsg}`);

      // Fallback: coba OWNER password jika sebelumnya pakai REMOTE_ADMIN
      if (remotePass !== ownerPass && adminPass && adminPass !== ownerPass) {
        console.log(`[Sync] 🔄 Retry push dengan OWNER password...`);
        try {
          const retryRes = await fetch(`${url}/api/admin/accounts`, {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              "x-admin-password": ownerPass,
            },
            body: JSON.stringify({
              token: acc.token,
              recipeId: acc.recipeId,
              label: acc.label,
              email: acc.email,
              password: acc.password,
              runs: acc.runs || 0,
              expired: Boolean(acc.expired),
              archived: Boolean(acc.archived),
              archivedAt: acc.archivedAt || null,
              archivedMonth: acc.archivedMonth || null,
              archiveReason: acc.archiveReason || null,
              restoredAt: acc.restoredAt || null,
              googleBlocked: Boolean(acc.googleBlocked),
              googleBlockedAt: acc.googleBlockedAt || null,
              googleBlockedReason: acc.googleBlockedReason || null,
              figmaBlocked: Boolean(acc.figmaBlocked),
              figmaBlockedAt: acc.figmaBlockedAt || null,
              figmaBlockedReason: acc.figmaBlockedReason || null,
              updatedAt: acc.updatedAt || null,
            }),
          });
          if (retryRes.ok) {
            console.log(`[Sync] ✅ Retry dengan OWNER password berhasil!`);
          } else {
            const retryData = await retryRes.json().catch(() => ({}));
            console.error(`[Sync] ❌ Retry juga gagal: ${retryData.error || "Unknown"}`);
          }
        } catch (retryErr) {
          console.error(`[Sync] ❌ Error retry: ${retryErr.message}`);
        }
      }
    }
  } catch (err) {
    console.error(`[Sync] ❌ Error menghubungi remote server: ${err.message}`);
  }
}

export async function deleteFromRemoteServer(email) {
  const remoteUrl = process.env.REMOTE_SERVER_URL;
  const ownerPass = process.env.OWNER_PASSWORD || "4rg0v14N";
  const adminPass = process.env.REMOTE_ADMIN_PASSWORD || process.env.ADMIN_PASSWORD || "";
  const remotePass = ownerPass || adminPass;

  if (!remoteUrl || !email) return;

  const url = remoteUrl.replace(/\/$/, "");
  console.log(`[Sync] Menghapus akun ${email} di remote server...`);

  async function doDelete(pass) {
    return fetch(`${url}/api/admin/accounts/by-email?email=${encodeURIComponent(email)}`, {
      method: "DELETE",
      headers: { "x-admin-password": pass },
    });
  }

  try {
    const res = await doDelete(remotePass);
    if (res.ok) {
      console.log(`[Sync] ✅ Akun ${email} berhasil dihapus di remote server.`);
    } else {
      const data = await res.json().catch(() => ({}));
      console.error(`[Sync] ❌ Gagal menghapus di remote: ${data.error || "Unknown error"}`);
      // Retry dengan OWNER password
      if (remotePass !== ownerPass && adminPass && adminPass !== ownerPass) {
        console.log(`[Sync] 🔄 Retry delete dengan OWNER password...`);
        try {
          const retryRes = await doDelete(ownerPass);
          if (retryRes.ok) console.log(`[Sync] ✅ Retry delete berhasil!`);
        } catch (_) {}
      }
    }
  } catch (err) {
    console.error(`[Sync] ❌ Error hapus remote: ${err.message}`);
  }
}

export async function syncWithRemoteServer() {
  const remoteUrl = process.env.REMOTE_SERVER_URL;
  const ownerPass = process.env.OWNER_PASSWORD || "4rg0v14N";
  const adminPass = process.env.REMOTE_ADMIN_PASSWORD || process.env.ADMIN_PASSWORD || "";
  const remotePass = ownerPass || adminPass;

  if (!remoteUrl) return { ok: false, error: "Sinkronisasi hanya dapat dijalankan dari dashboard Localhost (PC/RDP) karena cPanel berfungsi sebagai server penerima." };

  const url = remoteUrl.replace(/\/$/, "");
  console.log(`[Sync] Sinkronisasi data dengan remote server di ${url}...`);

  async function doFetch(pass) {
    return fetch(`${url}/api/admin/accounts/raw`, {
      method: "GET",
      headers: { "x-admin-password": pass },
    });
  }

  try {
    let res = await doFetch(remotePass);

    // Jika gagal, retry dengan OWNER password
    if (!res.ok && remotePass !== ownerPass && adminPass && adminPass !== ownerPass) {
      const data = await res.json().catch(() => ({}));
      const errMsg = data.error || `HTTP ${res.status}`;
      console.log(`[Sync] ⚠️ Gagal dengan REMOTE_ADMIN (${errMsg}), mencoba OWNER password...`);
      res = await doFetch(ownerPass);
    }

    if (!res.ok) {
      const data = await res.json().catch(() => ({}));
      throw new Error(data.error || `HTTP ${res.status}`);
    }

    const { accounts: remoteAccountsRaw } = await res.json();
    const { accounts: remoteAccounts } = normalizeAccountList(Array.isArray(remoteAccountsRaw) ? remoteAccountsRaw : []);
    if (!Array.isArray(remoteAccounts)) {
      throw new Error("Invalid response format from remote server");
    }

    const localAccounts = readAll();
    let updated = false;

    // Load blacklist email yang sudah dihapus
    const deletedEmails = readDeletedEmails();

    // 1. Merge remote accounts into local database
    for (const rAcc of remoteAccounts) {
      // Skip akun yang ada di blacklist hapus
      if (rAcc.email && deletedEmails.includes(rAcc.email)) {
        console.log(`[Sync] ⛔ Skip akun ${rAcc.email} — ada di blacklist hapus.`);
        continue;
      }

      const lIdx = localAccounts.findIndex(a => a.email === rAcc.email && rAcc.email !== "");
      if (lIdx < 0) {
        // Akun baru dari remote, masukkan ke lokal
        localAccounts.push({
          id: rAcc.id || "acc_" + Math.random().toString(36).slice(2, 12),
          label: rAcc.label,
          token: rAcc.token,
          recipeId: rAcc.recipeId,
          email: rAcc.email,
          password: rAcc.password,
          addedAt: rAcc.addedAt || new Date().toISOString(),
          updatedAt: rAcc.updatedAt || rAcc.addedAt || new Date().toISOString(),
          lastUsedAt: rAcc.lastUsedAt || null,
          runs: rAcc.runs || 0,
          expired: Boolean(rAcc.expired),
          archived: Boolean(rAcc.archived),
          archivedAt: rAcc.archivedAt || null,
          archivedMonth: rAcc.archivedMonth || null,
          archiveReason: rAcc.archiveReason || null,
          restoredAt: rAcc.restoredAt || null,
          googleBlocked: Boolean(rAcc.googleBlocked),
          googleBlockedAt: rAcc.googleBlockedAt || null,
          googleBlockedReason: rAcc.googleBlockedReason || null,
          figmaBlocked: Boolean(rAcc.figmaBlocked),
          figmaBlockedAt: rAcc.figmaBlockedAt || null,
          figmaBlockedReason: rAcc.figmaBlockedReason || null,
        });
        updated = true;
        console.log(`[Sync] Akun baru diunduh: ${rAcc.email}`);
      } else {
        // Akun sudah ada di lokal, update jika remote lebih baru atau tokennya beda
        const lAcc = localAccounts[lIdx];

        // ─── Tentukan versi mana yang lebih baru ───
        const dLocal = decodeJwt(lAcc.token);
        const dRemote = decodeJwt(rAcc.token);
        const localTokenIat = dLocal?.iat || 0;
        const remoteTokenIat = dRemote?.iat || 0;

        const localUpdatedAt = lAcc.updatedAt ? new Date(lAcc.updatedAt).getTime() : 0;
        const remoteUpdatedAt = rAcc.updatedAt ? new Date(rAcc.updatedAt).getTime() : 0;

        let isRemoteNewer = false;
        if (remoteTokenIat > localTokenIat) {
          isRemoteNewer = true;
        } else if (remoteTokenIat === localTokenIat) {
          if (remoteUpdatedAt > localUpdatedAt) {
            isRemoteNewer = true;
          }
        }

        let isLocalNewer = false;
        if (localTokenIat > remoteTokenIat) {
          isLocalNewer = true;
        } else if (localTokenIat === remoteTokenIat) {
          if (localUpdatedAt > remoteUpdatedAt) {
            isLocalNewer = true;
          }
        }

        // Deteksi apakah local baru saja di-restore (restoredAt > remote archivedAt)
        const lRestored = lAcc.restoredAt ? new Date(lAcc.restoredAt).getTime() : 0;
        const rArchivedAt = rAcc.archivedAt ? new Date(rAcc.archivedAt).getTime() : 0;
        const isLocalRestored = lRestored > rArchivedAt && lRestored > 0;

        // Runs: jika local baru direstore, pakai runs local (bukan Math.max)
        const maxRuns = isLocalRestored ? (lAcc.runs || 0) : Math.max(lAcc.runs || 0, rAcc.runs || 0);

        // Archived: jika local restore lebih baru, pertahankan unarchived
        const mergedArchived = isLocalRestored ? false : (isRemoteNewer ? Boolean(rAcc.archived) : Boolean(lAcc.archived || rAcc.archived));
        const mergedArchiveMonth = mergedArchived ? (lAcc.archivedMonth || rAcc.archivedMonth || getCurrentBillingMonth()) : null;
        const mergedArchiveReason = mergedArchived ? (lAcc.archiveReason || rAcc.archiveReason || "runs_limit") : null;
        const mergedArchivedAt = mergedArchived ? (lAcc.archivedAt || rAcc.archivedAt || new Date().toISOString()) : null;
        const mergedRestoredAt = !mergedArchived ? (lAcc.restoredAt || rAcc.restoredAt || null) : null;
        
        // googleBlocked & figmaBlocked: jika salah satu sudah diblokir, pertahankan true (permanen)
        const mergedGoogleBlocked = Boolean(lAcc.googleBlocked || rAcc.googleBlocked);
        const mergedFigmaBlocked = Boolean(lAcc.figmaBlocked || rAcc.figmaBlocked);
        const mergedGoogleBlockedAt = mergedGoogleBlocked ? (lAcc.googleBlockedAt || rAcc.googleBlockedAt || null) : null;
        const mergedGoogleBlockedReason = mergedGoogleBlocked ? (lAcc.googleBlockedReason || rAcc.googleBlockedReason || null) : null;
        const mergedFigmaBlockedAt = mergedFigmaBlocked ? (lAcc.figmaBlockedAt || rAcc.figmaBlockedAt || null) : null;
        const mergedFigmaBlockedReason = mergedFigmaBlocked ? (lAcc.figmaBlockedReason || rAcc.figmaBlockedReason || null) : null;

        // Cek apakah ada perbedaan data antara lokal dan remote
        const hasDiff = 
          lAcc.token !== rAcc.token ||
          lAcc.label !== rAcc.label ||
          lAcc.password !== rAcc.password ||
          lAcc.recipeId !== rAcc.recipeId ||
          lAcc.expired !== rAcc.expired ||
          lAcc.runs !== rAcc.runs ||
          lAcc.maxRuns !== rAcc.maxRuns ||
          lAcc.credits !== rAcc.credits ||
          Boolean(lAcc.archived) !== Boolean(rAcc.archived) ||
          Boolean(lAcc.googleBlocked) !== Boolean(rAcc.googleBlocked) ||
          Boolean(lAcc.figmaBlocked) !== Boolean(rAcc.figmaBlocked);

        if (hasDiff) {
          if (isRemoteNewer) {
            // Update lokal dengan data dari remote
            lAcc.token = rAcc.token;
            lAcc.recipeId = rAcc.recipeId || lAcc.recipeId;
            lAcc.password = rAcc.password || lAcc.password;
            lAcc.label = rAcc.label || lAcc.label;
            lAcc.expired = Boolean(rAcc.expired);
            lAcc.runs = rAcc.runs || 0;
            lAcc.maxRuns = rAcc.maxRuns || lAcc.maxRuns;
            lAcc.credits = rAcc.credits || lAcc.credits;
            lAcc.lastUsedAt = rAcc.lastUsedAt || lAcc.lastUsedAt;
            lAcc.archived = Boolean(rAcc.archived);
            lAcc.archivedAt = rAcc.archivedAt;
            lAcc.archivedMonth = rAcc.archivedMonth;
            lAcc.archiveReason = rAcc.archiveReason;
            lAcc.restoredAt = rAcc.restoredAt;
            lAcc.googleBlocked = mergedGoogleBlocked;
            lAcc.googleBlockedAt = mergedGoogleBlockedAt;
            lAcc.googleBlockedReason = mergedGoogleBlockedReason;
            lAcc.figmaBlocked = mergedFigmaBlocked;
            lAcc.figmaBlockedAt = mergedFigmaBlockedAt;
            lAcc.figmaBlockedReason = mergedFigmaBlockedReason;
            lAcc.updatedAt = rAcc.updatedAt || new Date().toISOString();
            updated = true;
            console.log(`[Sync] Mengupdate akun lokal (Remote lebih baru): ${rAcc.email}`);
          } else if (isLocalNewer) {
            // Lokal lebih baru, kirim data lokal ke remote
            console.log(`[Sync] Data lokal lebih baru untuk ${lAcc.email}. Mengupdate remote...`);
            pushToRemoteServer(lAcc).catch(() => {});
          } else {
            // Jika status kebaruannya sama (misal token sama, tapi ada perbedaan runs atau archived)
            // Lakukan merging standard
            const needsLocalUpdate =
              lAcc.token !== rAcc.token ||
              lAcc.expired !== rAcc.expired ||
              lAcc.runs !== maxRuns ||
              Boolean(lAcc.archived) !== mergedArchived ||
              Boolean(lAcc.googleBlocked) !== mergedGoogleBlocked ||
              Boolean(lAcc.figmaBlocked) !== mergedFigmaBlocked;

            const needsRemoteUpdate =
              rAcc.runs < maxRuns ||
              (!rAcc.expired && lAcc.expired) ||
              Boolean(rAcc.archived) !== mergedArchived ||
              Boolean(rAcc.googleBlocked) !== mergedGoogleBlocked ||
              Boolean(rAcc.figmaBlocked) !== mergedFigmaBlocked;

            if (needsLocalUpdate) {
              lAcc.token = rAcc.token;
              lAcc.recipeId = rAcc.recipeId || lAcc.recipeId;
              lAcc.password = rAcc.password || lAcc.password;
              lAcc.label = rAcc.label || lAcc.label;
              lAcc.expired = Boolean(rAcc.expired);
              lAcc.runs = maxRuns;
              lAcc.lastUsedAt = rAcc.lastUsedAt || lAcc.lastUsedAt;
              lAcc.archived = mergedArchived;
              lAcc.archivedAt = mergedArchivedAt;
              lAcc.archivedMonth = mergedArchiveMonth;
              lAcc.archiveReason = mergedArchiveReason;
              lAcc.restoredAt = mergedRestoredAt;
              lAcc.googleBlocked = mergedGoogleBlocked;
              lAcc.googleBlockedAt = mergedGoogleBlockedAt;
              lAcc.googleBlockedReason = mergedGoogleBlockedReason;
              lAcc.figmaBlocked = mergedFigmaBlocked;
              lAcc.figmaBlockedAt = mergedFigmaBlockedAt;
              lAcc.figmaBlockedReason = mergedFigmaBlockedReason;
              lAcc.updatedAt = new Date().toISOString();
              updated = true;
              console.log(`[Sync] Mengupdate akun lokal (Standard Merge): ${rAcc.email} (runs: ${maxRuns})`);
            }

            if (needsRemoteUpdate) {
              console.log(`[Sync] Mengupdate remote (Standard Merge): ${lAcc.email}`);
              pushToRemoteServer(lAcc).catch(() => {});
            }
          }
        }
      }
    }

    // 2. Upload local accounts that are missing on remote
    for (const lAcc of localAccounts) {
      const rIdx = remoteAccounts.findIndex(a => a.email === lAcc.email && lAcc.email !== "");
      const lMaxRuns = lAcc.maxRuns || DEFAULT_MAX_RUNS;
      if (rIdx < 0 && lAcc.email && !lAcc.expired && !lAcc.archived && (lAcc.runs || 0) < lMaxRuns && !isExpired(lAcc.token)) {
        console.log(`[Sync] Akun lokal ${lAcc.email} tidak ada di remote. Mengupload...`);
        await pushToRemoteServer(lAcc).catch(() => {});
      }
    }

    if (updated) {
      writeAll(localAccounts);
      console.log("[Sync] ✅ Sinkronisasi lokal database selesai.");
    } else {
      console.log("[Sync] ✅ Semua data sudah sinkron.");
    }

    return { ok: true, msg: "Synchronization successful" };
  } catch (err) {
    console.error(`[Sync] ❌ Error sinkronisasi: ${err.message}`);
    return { ok: false, error: err.message };
  }
}

export function addAccount({
  token,
  recipeId = "",
  label = "",
  email = "",
  password = "",
  runs = undefined,
  maxRuns = undefined,
  credits = undefined,
  expired = undefined,
  archived = undefined,
  archivedAt = undefined,
  archivedMonth = undefined,
  archiveReason = undefined,
  restoredAt = undefined,
  googleBlocked = undefined,
  googleBlockedAt = undefined,
  googleBlockedReason = undefined,
  figmaBlocked = undefined,
  figmaBlockedAt = undefined,
  figmaBlockedReason = undefined,
  updatedAt = undefined,
}) {
  const decoded = decodeJwt(token);
  if (!decoded) throw new Error("Invalid JWT token");

  const list = readAll();
  const targetEmail = email || decoded.email || "";
  
  // Check if account already exists by email
  const existingIdx = list.findIndex(a => a.email === targetEmail && targetEmail !== "");

  if (existingIdx >= 0) {
    const acc = list[existingIdx];
    // Deteksi apakah ini push restore (runs di-reset ke 0, archived false)
    const isRestorePush = restoredAt && archived === false && (runs || 0) === 0;

    if (acc.token !== token) {
      acc.token = token;
      acc.expired = expired !== undefined ? expired : false;
      // Saat restore: pakai runs incoming (0), jangan Math.max
      acc.runs = runs !== undefined ? (isRestorePush ? runs : Math.max(acc.runs || 0, runs)) : (acc.runs || 0);
      acc.lastUsedAt = acc.lastUsedAt || null;
    } else {
      if (runs !== undefined) acc.runs = isRestorePush ? runs : Math.max(acc.runs || 0, runs);
      if (expired !== undefined) acc.expired = expired;
    }
    if (recipeId) acc.recipeId = recipeId;
    if (password) acc.password = password;
    if (label) acc.label = label;
    if (maxRuns !== undefined) acc.maxRuns = maxRuns;
    if (credits !== undefined) acc.credits = credits;
    if (archived !== undefined) acc.archived = archived;
    if (archivedAt !== undefined) acc.archivedAt = archivedAt;
    if (archivedMonth !== undefined) acc.archivedMonth = archivedMonth;
    if (archiveReason !== undefined) acc.archiveReason = archiveReason;
    if (restoredAt !== undefined) acc.restoredAt = restoredAt;
    if (googleBlocked !== undefined) acc.googleBlocked = googleBlocked;
    if (googleBlockedAt !== undefined) acc.googleBlockedAt = googleBlockedAt;
    if (googleBlockedReason !== undefined) acc.googleBlockedReason = googleBlockedReason;
    if (figmaBlocked !== undefined) acc.figmaBlocked = figmaBlocked;
    if (figmaBlockedAt !== undefined) acc.figmaBlockedAt = figmaBlockedAt;
    if (figmaBlockedReason !== undefined) acc.figmaBlockedReason = figmaBlockedReason;
    
    // Update updatedAt
    acc.updatedAt = updatedAt || new Date().toISOString();

    const normalized = normalizeAccountState(acc).account;
    list[existingIdx] = normalized;
    writeAll(list);
    
    // Sync to remote
    pushToRemoteServer(normalized).catch((err) => {
      console.error("[Sync] Gagal mengirim update ke remote server:", err.message);
    });
    
    return normalized;
  }

  const id = "acc_" + Math.random().toString(36).slice(2, 12);
  const acc = {
    id,
    label: label || decoded.email || decoded.name || "Akun",
    token,
    recipeId,
    email: targetEmail,
    password: password || "",
    addedAt: new Date().toISOString(),
    updatedAt: updatedAt || new Date().toISOString(),
    lastUsedAt: null,
    runs: runs !== undefined ? runs : 0,
    maxRuns: maxRuns !== undefined ? maxRuns : DEFAULT_MAX_RUNS,
    credits: credits !== undefined ? credits : DEFAULT_MONTHLY_CREDITS,
    expired: expired !== undefined ? expired : false,
    archived: archived !== undefined ? archived : false,
    archivedAt: archivedAt !== undefined ? archivedAt : null,
    archivedMonth: archivedMonth !== undefined ? archivedMonth : null,
    archiveReason: archiveReason !== undefined ? archiveReason : null,
    restoredAt: restoredAt !== undefined ? restoredAt : null,
    googleBlocked: googleBlocked !== undefined ? googleBlocked : false,
    googleBlockedAt: googleBlockedAt !== undefined ? googleBlockedAt : null,
    googleBlockedReason: googleBlockedReason !== undefined ? googleBlockedReason : null,
    figmaBlocked: figmaBlocked !== undefined ? figmaBlocked : false,
    figmaBlockedAt: figmaBlockedAt !== undefined ? figmaBlockedAt : null,
    figmaBlockedReason: figmaBlockedReason !== undefined ? figmaBlockedReason : null,
  };
  const normalized = normalizeAccountState(acc).account;
  list.push(normalized);
  writeAll(list);

  // Sync to remote
  pushToRemoteServer(normalized).catch((err) => {
    console.error("[Sync] Gagal mengirim akun ke remote server:", err.message);
  });

  return normalized;
}

export function updateAccount(id, patch) {
  const list = readAll();
  const idx = list.findIndex((a) => a.id === id);
  if (idx < 0) throw new Error("Account not found");
  if (patch.token) {
    const decoded = decodeJwt(patch.token);
    if (!decoded) throw new Error("Invalid JWT token");
  }
  const updatedAt = new Date().toISOString();
  list[idx] = normalizeAccountState({ ...list[idx], ...patch, updatedAt }).account;
  writeAll(list);

  // Sync to remote for all updates (such as expired status or runs)
  pushToRemoteServer(list[idx]).catch((err) => {
    console.error("[Sync] Gagal mengirim update ke remote server:", err.message);
  });

  return list[idx];
}


export function deleteAccount(id) {
  const list = readAll();
  const idx = list.findIndex((a) => a.id === id);
  if (idx < 0) return false;
  const acc = list[idx];
  list.splice(idx, 1);
  writeAll(list);
  
  // Tambahkan ke blacklist agar tidak di-import ulang oleh sync
  if (acc.email) {
    addDeletedEmail(acc.email);
    deleteFromRemoteServer(acc.email).catch((err) => {
      console.error("[Sync] Gagal mengirim delete ke remote server:", err.message);
    });
  }
  return true;
}

export function deleteAccounts(ids) {
  const list = readAll();
  const deletedEmailsList = [];
  const remaining = list.filter((a) => {
    if (ids.includes(a.id)) {
      if (a.email) deletedEmailsList.push(a.email);
      return false;
    }
    return true;
  });
  
  if (list.length === remaining.length) return 0;
  
  writeAll(remaining);
  
  // Tambahkan ke blacklist dan sync hapus ke remote
  for (const email of deletedEmailsList) {
    addDeletedEmail(email);
    deleteFromRemoteServer(email).catch((err) => {
      console.error("[Sync] Gagal mengirim delete ke remote server:", err.message);
    });
  }
  
  return deletedEmailsList.length;
}

export function archiveAccounts(ids, { reason = "manual" } = {}) {
  const idSet = new Set(Array.isArray(ids) ? ids : []);
  if (!idSet.size) return 0;

  const list = readAll();
  const currentMonth = getCurrentBillingMonth();
  const archivedAt = new Date().toISOString();
  const changedAccounts = [];

  for (let i = 0; i < list.length; i++) {
    const acc = list[i];
    if (!idSet.has(acc.id) || acc.archived) continue;
    list[i] = normalizeAccountState({
      ...acc,
      archived: true,
      archivedAt,
      archivedMonth: currentMonth,
      archiveReason: reason || "manual",
      expired: false,
      updatedAt: archivedAt,
    }).account;
    changedAccounts.push(list[i]);
  }

  if (!changedAccounts.length) return 0;

  writeAll(list);

  for (const acc of changedAccounts) {
    pushToRemoteServer(acc).catch((err) => {
      console.error("[Sync] Gagal mengirim archive akun ke remote server:", err.message);
    });
  }

  return changedAccounts.length;
}

export function deleteAccountByEmail(email) {
  const list = readAll();
  const idx = list.findIndex((a) => a.email === email);
  if (idx < 0) return false;
  list.splice(idx, 1);
  writeAll(list);
  return true;
}

export function markUsed(id) {
  const list = readAll();
  const idx = list.findIndex((a) => a.id === id);
  if (idx < 0) return;
  const nowStr = new Date().toISOString();
  list[idx].lastUsedAt = nowStr;
  list[idx].runs = (list[idx].runs || 0) + 1;
  list[idx].updatedAt = nowStr;
  list[idx] = normalizeAccountState(list[idx]).account;
  writeAll(list);

  // Immediately sync runs increment to remote server
  pushToRemoteServer(list[idx]).catch((err) => {
    console.error("[Sync] Gagal mengirim update runs ke remote server:", err.message);
  });
}

// ─── Pool Account Settings ─────────────────────────────────────────────────────
// Settable from admin dashboard, applies to all pool accounts.

export function getPoolSettings() {
  return {
    defaultMaxRuns: DEFAULT_MAX_RUNS,
    monthlyCredits: DEFAULT_MONTHLY_CREDITS,
  };
}

export function updatePoolSettings({ maxRuns, monthlyCredits }) {
  if (typeof maxRuns === "number" && maxRuns > 0) {
    DEFAULT_MAX_RUNS = maxRuns;
  }
  if (typeof monthlyCredits === "number" && monthlyCredits > 0) {
    DEFAULT_MONTHLY_CREDITS = monthlyCredits;
  }
}

// Per-account max runs override.  null / 0  → fall back to DEFAULT_MAX_RUNS.
export function setAccountMaxRuns(id, maxRuns) {
  const list = readAll();
  const idx = list.findIndex((a) => a.id === id);
  if (idx < 0) return null;
  list[idx].maxRuns = maxRuns > 0 ? maxRuns : undefined;
  list[idx] = normalizeAccountState(list[idx]).account;
  writeAll(list);
  return list[idx];
}

// ─── Monthly Reset ──────────────────────────────────────────────────────────────
// Resets all pool accounts: un-expire, runs → 0, credits → DEFAULT_MONTHLY_CREDITS.
// Only resets when the billing month actually changes (persisted to disk).

export function getLastMonthlyResetMonth() {
  ensureDir();
  try {
    if (fs.existsSync(MONTHLY_RESET_PATH)) {
      const data = JSON.parse(fs.readFileSync(MONTHLY_RESET_PATH, "utf8"));
      return data.month || null;
    }
  } catch {}
  return null;
}

function setLastMonthlyResetMonth(month) {
  ensureDir();
  try {
    fs.writeFileSync(MONTHLY_RESET_PATH, JSON.stringify({ month, updatedAt: new Date().toISOString() }, null, 2), "utf8");
  } catch (err) {
    console.error("[AccountStore] Failed to persist monthly reset month:", err.message);
  }
}

export function resetMonthlyLimits() {
  const list = readAll();
  const currentMonth = getCurrentBillingMonth();

  // Prevent duplicate reset within the same billing month (persisted check)
  const lastResetMonth = getLastMonthlyResetMonth();
  if (lastResetMonth === currentMonth) {
    return {
      total: list.length,
      active: list.filter((a) => !a.archived).length,
      reset: 0,
      month: currentMonth,
      skipped: true,
      reason: "Already reset for this billing month",
    };
  }

  let changed = false;
  let resetCount = 0;

  for (let i = 0; i < list.length; i++) {
    const a = list[i];

    // Un-archive runs_limit accounts when entering a new month
    if (a.archived && a.archiveReason === "runs_limit") {
      const archivedMonth = a.archivedMonth || getBillingMonthFromValue(a.archivedAt) || "";
      if (archivedMonth !== currentMonth) {
        a.archived = false;
        a.archivedAt = null;
        a.archivedMonth = null;
        a.archiveReason = null;
        a.restoredAt = new Date().toISOString();
        a.runs = 0;
        a.expired = false;
        resetCount++;
        changed = true;
      }
      continue;
    }

    // Skip other archived accounts (legacy monthly_limit handled by 31-day timer)
    if (a.archived) continue;

    // Reset expired state
    if (a.expired) {
      a.expired = false;
      a.expiredAt = null;
      a.expiredMonth = null;
      a.expireReason = null;
      changed = true;
    }

    // Reset runs (only on actual month change, not on every server restart)
    if (a.runs !== 0) {
      a.runs = 0;
      resetCount++;
      changed = true;
    }

    // Reset credits
    if ((a.credits || DEFAULT_MONTHLY_CREDITS) !== DEFAULT_MONTHLY_CREDITS) {
      a.credits = DEFAULT_MONTHLY_CREDITS;
      changed = true;
    }
  }

  if (changed) {
    writeAll(list);
  }

  // Persist the month so we don't reset again on restart
  setLastMonthlyResetMonth(currentMonth);

  return {
    total: list.length,
    active: list.filter((a) => !a.archived).length,
    reset: resetCount,
    month: currentMonth,
  };
}

// Pick the next available (non-expired) account.
// Prioritas: durasi token paling panjang → runs terbanyak → LRU
// excludeIds: Set of account IDs to skip (for retry logic)
// preferredId: try this ID first (legacy support)
export function pickAccount(excludeIds = null, { preferredId } = {}) {
  const excluded = excludeIds instanceof Set ? excludeIds : new Set();
  const nowSec = Date.now() / 1000;

  // Pre-decode token expiry untuk sorting
  const getTokenExp = (a) => {
    try {
      const d = decodeJwt(a.token);
      return d?.exp || 0;
    } catch { return 0; }
  };

  const list = readAll().filter((a) => {
    const maxRuns = typeof a.maxRuns === "number" && a.maxRuns > 0 ? a.maxRuns : DEFAULT_MAX_RUNS;
    return (
      !a.expired &&             // not manually marked expired (admin override)
      !a.archived &&            // akun auto-archived (runs_limit) tidak dipakai sampai 31 hari
      !a.googleBlocked &&       // akun diblokir Google — tidak akan dipilih
      !a.figmaBlocked &&        // akun diblokir Figma — tidak akan dipilih
      (a.runs || 0) < maxRuns && // masih ada sisa runs bulan ini
      !excluded.has(a.id)       // not in excluded set
    );
  });
  if (!list.length) return null;
  if (preferredId) {
    const p = list.find((a) => a.id === preferredId);
    if (p) return p;
  }
  // Sort: durasi token paling panjang → runs terbanyak → LRU
  list.sort((a, b) => {
    const expA = getTokenExp(a);
    const expB = getTokenExp(b);
    // 1. Token dengan sisa waktu paling panjang diprioritaskan
    if (expA !== expB) return expB - expA;
    // 2. Runs terbanyak (hampir limit) diprioritaskan
    const runsA = a.runs || 0;
    const runsB = b.runs || 0;
    if (runsA !== runsB) return runsB - runsA;
    // 3. Fallback LRU: never used → first; else oldest lastUsedAt
    if (!a.lastUsedAt && b.lastUsedAt) return -1;
    if (a.lastUsedAt && !b.lastUsedAt) return 1;
    if (!a.lastUsedAt && !b.lastUsedAt) return 0;
    return new Date(a.lastUsedAt) - new Date(b.lastUsedAt);
  });
  return list[0];
}

export function deleteExpiredAccounts() {
  const list = readAll();
  const remaining = [];
  const deletedEmailsList = [];
  const deletedIds = [];

  for (const a of list) {
    const jwtExpired = isExpired(a.token);
    const manuallyExpired = Boolean(a.expired);
    if (jwtExpired || manuallyExpired) {
      if (a.email) deletedEmailsList.push(a.email);
      deletedIds.push(a.id);
    } else {
      remaining.push(a);
    }
  }

  if (deletedIds.length === 0) return 0;

  writeAll(remaining);

  // Tambahkan ke blacklist dan sync hapus ke remote
  for (const email of deletedEmailsList) {
    addDeletedEmail(email);
    deleteFromRemoteServer(email).catch((err) => {
      console.error("[Sync] Gagal mengirim delete ke remote server:", err.message);
    });
  }

  return deletedIds.length;
}

export function restoreArchivedAccount(id, { force = false } = {}) {
  const list = readAll();
  const idx = list.findIndex((a) => a.id === id);
  if (idx < 0) throw new Error("Account not found");

  const acc = list[idx];
  if (acc.figmaBlocked || acc.googleBlocked) {
    throw new Error("Akun ini diblokir permanen (Figma/Google). Tidak dapat di-restore. Silakan buat akun baru.");
  }
  if (!acc.archived) {
    return acc;
  }

  const currentMonth = getCurrentBillingMonth();
  if (!force && acc.archivedMonth === currentMonth) {
    throw new Error("Akun archive ini masih berada di bulan kredit yang sama.");
  }

  const nowStr = new Date().toISOString();
  list[idx] = normalizeAccountState({
    ...acc,
    archived: false,
    archivedAt: null,
    archivedMonth: null,
    archiveReason: null,
    restoredAt: nowStr,
    runs: 0,
    expired: false,
    updatedAt: nowStr,
  }).account;
  writeAll(list);

  // Push ke remote — fire and forget dengan retry
  const restored = list[idx];
  pushToRemoteServer(restored).then(() => {
    console.log(`[Sync] ✅ Restore akun ${restored.label} berhasil dipush ke remote.`);
  }).catch(async (err) => {
    console.error(`[Sync] ❌ Gagal push restore ${restored.label}: ${err.message}, retry...`);
    // Retry sekali setelah 3 detik
    await new Promise(r => setTimeout(r, 3000));
    pushToRemoteServer(restored).catch(e => {
      console.error(`[Sync] ❌ Retry push restore juga gagal: ${e.message}`);
    });
  });

  return restored;
}

export function deleteAllArchivedAccounts() {
  const list = readAll();
  const deletedEmailsList = [];
  const remaining = [];

  for (const a of list) {
    if (a.archived) {
      if (a.email) deletedEmailsList.push(a.email);
    } else {
      remaining.push(a);
    }
  }

  const deletedCount = deletedEmailsList.length;
  if (deletedCount === 0) return 0;

  writeAll(remaining);

  // Blacklist and sync delete to remote
  for (const email of deletedEmailsList) {
    addDeletedEmail(email);
    deleteFromRemoteServer(email).catch((err) => {
      console.error("[Sync] Gagal mengirim delete ke remote server:", err.message);
    });
  }

  return deletedCount;
}

export function deleteAllActiveAccounts() {
  const list = readAll();
  const deletedEmailsList = [];
  const remaining = [];

  for (const a of list) {
    if (!a.archived) {
      if (a.email) deletedEmailsList.push(a.email);
    } else {
      remaining.push(a);
    }
  }

  const deletedCount = deletedEmailsList.length;
  if (deletedCount === 0) return 0;

  writeAll(remaining);

  // Blacklist and sync delete to remote
  for (const email of deletedEmailsList) {
    addDeletedEmail(email);
    deleteFromRemoteServer(email).catch((err) => {
      console.error("[Sync] Gagal mengirim delete ke remote server:", err.message);
    });
  }

  return deletedCount;
}

