import "dotenv/config";
import express from "express";
import multer from "multer";
import path from "path";
import { fileURLToPath } from "url";
import { buildExecutePayload } from "./lib/weavy-payload.js";
import * as Accounts from "./lib/account-store.js";
import { pollBatchStatus } from "./lib/weavy-poll.js";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const app = express();
const PORT = process.env.PORT || 3000;
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || "admin123";

const WEAVY_API = "https://api.weavy.ai";
const DEFAULT_RECIPE_ID = process.env.WEAVY_RECIPE_ID || "YDyaydo2aT1abHpDqPPy8a";

app.use(express.json({ limit: "10mb" }));
app.use(express.static(path.join(__dirname, "public")));

// Friendly route for /admin
app.get("/admin", (_req, res) => {
  res.sendFile(path.join(__dirname, "public", "admin.html"));
});

const upload = multer({
  storage: multer.memoryStorage(),
  limits: { fileSize: 200 * 1024 * 1024 },
});

async function readResponse(res) {
  const text = await res.text();
  let json = null;
  try { json = text ? JSON.parse(text) : null; } catch {}
  return { status: res.status, ok: res.ok, json, text };
}

function extractWeavyError(payload) {
  if (!payload) return "Unknown Weavy error";
  if (typeof payload === "string") return payload;
  if (payload.json) {
    const j = payload.json;
    if (typeof j === "string") return j;
    if (j.statusCode === 403 && j.internalErrorCode === 1076) {
      return `Model "${j.message}" tidak tersedia di plan akun ini.`;
    }
    if (j.message && j.statusCode) return `[${j.statusCode}] ${j.message}${j.internalErrorCode ? ` (code ${j.internalErrorCode})` : ""}`;
    if (j.message) return j.message;
    if (j.error) return typeof j.error === "string" ? j.error : JSON.stringify(j.error);
    if (j.detail) return typeof j.detail === "string" ? j.detail : JSON.stringify(j.detail);
    return JSON.stringify(j);
  }
  return payload.text || `HTTP ${payload.status}`;
}

async function weavyFetch(p, { method = "GET", token, body, extraHeaders } = {}) {
  const headers = {
    accept: "application/json, text/plain, */*",
    "content-type": "application/json",
    "x-weavy-auth-provider": "firebase",
    "x-app-version": "4.1.246",
    authorization: `Bearer ${token}`,
    ...extraHeaders,
  };
  return fetch(`${WEAVY_API}${p}`, {
    method,
    headers,
    body: body ? JSON.stringify(body) : undefined,
  });
}

// ─── Admin auth ─────────────────────────────────────────────────────────────

function requireAdmin(req, res, next) {
  const pass = req.headers["x-admin-password"];
  if (pass !== ADMIN_PASSWORD) return res.status(401).json({ error: "Unauthorized" });
  next();
}

// ─── Admin API: account management ──────────────────────────────────────────

app.post("/api/admin/login", (req, res) => {
  const { password } = req.body || {};
  if (password !== ADMIN_PASSWORD) return res.status(401).json({ error: "Wrong password" });
  res.json({ ok: true });
});

app.get("/api/admin/accounts", requireAdmin, (_req, res) => {
  res.json({ accounts: Accounts.listAccountsSafe() });
});

app.post("/api/admin/accounts", requireAdmin, (req, res) => {
  try {
    const { token, recipeId, label } = req.body || {};
    if (!token) return res.status(400).json({ error: "token required" });
    const acc = Accounts.addAccount({
      token: token.trim(),
      recipeId: recipeId?.trim() || "",
      label: label?.trim() || "",
    });
    res.json({ account: acc });
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

app.put("/api/admin/accounts/:id", requireAdmin, (req, res) => {
  try {
    const acc = Accounts.updateAccount(req.params.id, req.body || {});
    res.json({ account: acc });
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

app.delete("/api/admin/accounts/:id", requireAdmin, (req, res) => {
  const ok = Accounts.deleteAccount(req.params.id);
  if (!ok) return res.status(404).json({ error: "Not found" });
  res.json({ ok: true });
});

// Bulk import: paste many tokens at once (one per line, optionally TAB-separated with recipeId & label)
app.post("/api/admin/accounts/bulk", requireAdmin, (req, res) => {
  const { lines } = req.body || {};
  if (!Array.isArray(lines)) return res.status(400).json({ error: "lines: string[] required" });

  const added = [];
  const errors = [];
  for (const line of lines) {
    const trimmed = String(line).trim();
    if (!trimmed) continue;
    const parts = trimmed.split(/\t|,/).map((s) => s.trim());
    const [token, recipeId = "", label = ""] = parts;
    try {
      added.push(Accounts.addAccount({ token, recipeId, label }));
    } catch (e) {
      errors.push({ line: trimmed.slice(0, 40), error: e.message });
    }
  }
  res.json({ added: added.length, errors });
});

// ─── Public config ──────────────────────────────────────────────────────────

app.get("/api/config", (_req, res) => {
  const accs = Accounts.listAccountsSafe();
  const available = accs.filter((a) => !a.expired).length;
  res.json({
    defaultRecipeId: DEFAULT_RECIPE_ID,
    totalAccounts: accs.length,
    availableAccounts: available,
  });
});

// ─── User-facing API: upload + generate (auto-pick account) ─────────────────

app.post("/api/upload", upload.single("file"), async (req, res) => {
  try {
    if (!req.file) return res.status(400).json({ error: "No file provided" });

    const acc = Accounts.pickAccount();
    if (!acc) return res.status(503).json({ error: "Tidak ada akun Weavy yang tersedia. Hubungi admin." });

    const fd = new FormData();
    const blob = new Blob([req.file.buffer], { type: req.file.mimetype });
    fd.append("file", blob, req.file.originalname);
    fd.append("type", req.file.mimetype);

    const r = await fetch(`${WEAVY_API}/api/v1/assets/upload`, {
      method: "POST",
      headers: {
        accept: "application/json, text/plain, */*",
        authorization: `Bearer ${acc.token}`,
        "x-weavy-auth-provider": "firebase",
        "x-app-version": "4.1.246",
      },
      body: fd,
    });

    const data = await readResponse(r);
    if (!r.ok) {
      console.error("Upload failed:", r.status, data.json || data.text);
      return res.status(r.status).json({ error: extractWeavyError(data), accountId: acc.id });
    }
    res.json({ ...data.json, _accountUsed: acc.label });
  } catch (err) {
    console.error("Upload error:", err);
    res.status(500).json({ error: err.message });
  }
});

app.post("/api/generate", async (req, res) => {
  try {
    const {
      imageUrl, imageMeta,
      videoUrl, videoMeta,
      prompt = "",
      modelType = "Standard",
      version = "V2.6",
      keepOriginalSound = true,
      characterOrientation = "image",
      numberOfRuns = 1,
    } = req.body || {};

    if (!imageUrl || !videoUrl) return res.status(400).json({ error: "imageUrl and videoUrl required" });
    if (!["image", "video"].includes(characterOrientation)) return res.status(400).json({ error: "invalid characterOrientation" });
    if (!["Pro", "Standard"].includes(modelType)) return res.status(400).json({ error: "invalid modelType" });
    if (!["V2.6", "V3.0"].includes(version)) return res.status(400).json({ error: "invalid version" });

    const acc = Accounts.pickAccount();
    if (!acc) return res.status(503).json({ error: "Tidak ada akun Weavy yang tersedia. Hubungi admin." });

    const recipeId = acc.recipeId || DEFAULT_RECIPE_ID;
    const payload = buildExecutePayload({
      imageUrl, imageMeta, videoUrl, videoMeta,
      prompt, modelType, version, keepOriginalSound, characterOrientation, numberOfRuns,
    });

    const r = await weavyFetch(`/api/v1/batches/recipes/${recipeId}/execute`, {
      method: "POST",
      token: acc.token,
      body: payload,
      extraHeaders: { "x-app-recipeid": recipeId },
    });

    const data = await readResponse(r);
    if (!r.ok) {
      console.error("Generate failed:", r.status, data.json || data.text);
      return res.status(r.status).json({ error: extractWeavyError(data), accountUsed: acc.label });
    }

    Accounts.markUsed(acc.id);

    res.json({
      ...data.json,
      _accountId: acc.id,
      _accountLabel: acc.label,
      _recipeId: recipeId,
    });
  } catch (err) {
    console.error("Generate error:", err);
    res.status(500).json({ error: err.message });
  }
});

// Poll: official Weavy status endpoint
//   GET /api/v1/batches/recipes/{recipeId}/batches/{batchId}/status
app.get("/api/poll/:accountId/:batchId", async (req, res) => {
  const { accountId, batchId } = req.params;
  const recipeIdQ = req.query.recipeId;

  const acc = Accounts.getAccount(accountId);
  if (!acc) return res.status(404).json({ error: "Account not found" });

  const recipeId = recipeIdQ || acc.recipeId || DEFAULT_RECIPE_ID;

  try {
    const result = await pollBatchStatus({ token: acc.token, recipeId, batchId });

    if (!result.ok) {
      return res.status(result.status || 500).json({ error: result.error || "Status check failed" });
    }

    res.json({
      found: Boolean(result.url),
      url: result.url || null,
      status: result.status,
      recipeId,
      batchId,
      raw: result.raw,
    });
  } catch (err) {
    console.error("Poll error:", err);
    res.status(500).json({ error: err.message });
  }
});

// Health
app.get("/api/health", (_req, res) => res.json({ ok: true }));

app.listen(PORT, () => {
  console.log(`Kling Motion (Weavy) app running at http://localhost:${PORT}`);
  console.log(`Admin password: ${ADMIN_PASSWORD === "admin123" ? "admin123 (default — ganti via .env ADMIN_PASSWORD)" : "(custom)"}`);
  console.log(`Default recipe: ${DEFAULT_RECIPE_ID}`);
});
