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

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");

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

function readAll() {
  ensureDir();
  if (!fs.existsSync(STORE_PATH)) return [];
  try {
    return JSON.parse(fs.readFileSync(STORE_PATH, "utf-8"));
  } 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() {
  // Return without raw token (for public listing)
  return readAll().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,
      email: decoded?.email,
      exp: decoded?.exp,
      expired: jwtExpired || Boolean(a.expired) || (a.runs || 0) >= 3,
      tokenPreview: a.token.slice(0, 24) + "..." + a.token.slice(-12),
    };
  });
}

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 remotePass = process.env.REMOTE_ADMIN_PASSWORD || process.env.ADMIN_PASSWORD;

  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,
      }),
    });

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

export async function syncWithRemoteServer() {
  const remoteUrl = process.env.REMOTE_SERVER_URL;
  const remotePass = process.env.REMOTE_ADMIN_PASSWORD || process.env.ADMIN_PASSWORD;

  if (!remoteUrl) return { ok: false, error: "REMOTE_SERVER_URL not configured" };

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

  try {
    const res = await fetch(`${url}/api/admin/accounts/raw`, {
      method: "GET",
      headers: {
        "x-admin-password": remotePass,
      },
    });

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

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

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

    // 1. Merge remote accounts into local database
    for (const rAcc of remoteAccounts) {
      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(),
          lastUsedAt: rAcc.lastUsedAt || null,
          runs: rAcc.runs || 0,
          expired: Boolean(rAcc.expired),
        });
        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];
        if (lAcc.token !== rAcc.token || lAcc.expired !== rAcc.expired || lAcc.runs !== rAcc.runs) {
          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;
          lAcc.lastUsedAt = rAcc.lastUsedAt || lAcc.lastUsedAt;
          updated = true;
          console.log(`[Sync] Mengupdate akun lokal: ${rAcc.email}`);
        }
      }
    }

    // 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 !== "");
      if (rIdx < 0 && lAcc.email) {
        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 = "" }) {
  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];
    acc.token = token;
    if (recipeId) acc.recipeId = recipeId;
    if (password) acc.password = password;
    if (label) acc.label = label;
    acc.expired = false;
    acc.runs = 0; // reset runs count since it's a new/revived token
    acc.lastUsedAt = null;
    writeAll(list);
    
    // Sync to remote
    pushToRemoteServer(acc).catch((err) => {
      console.error("[Sync] Gagal mengirim update ke remote server:", err.message);
    });
    
    return acc;
  }

  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(),
    lastUsedAt: null,
    runs: 0,
  };
  list.push(acc);
  writeAll(list);

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

  return acc;
}

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");
  }
  list[idx] = { ...list[idx], ...patch };
  writeAll(list);

  // Sync to remote if we updated the token or password
  if (patch.token || patch.password) {
    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;
  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;
  list[idx].lastUsedAt = new Date().toISOString();
  list[idx].runs = (list[idx].runs || 0) + 1;
  writeAll(list);
}

// Pick the next available (non-expired) account using least-recently-used.
// 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 list = readAll().filter((a) =>
    !isExpired(a.token) &&   // JWT not expired
    !a.expired &&            // not manually marked expired (e.g. after 403)
    (a.runs || 0) < 3 &&     // limit 3 generations per account
    !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;
  }
  // LRU: never used → first; else oldest lastUsedAt
  list.sort((a, b) => {
    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 filtered = list.filter((a) => {
    const jwtExpired = isExpired(a.token);
    const hasExhaustedRuns = (a.runs || 0) >= 3;
    const manuallyExpired = Boolean(a.expired);
    return !jwtExpired && !hasExhaustedRuns && !manuallyExpired;
  });
  const deletedCount = list.length - filtered.length;
  writeAll(filtered);
  return deletedCount;
}
