// CapCut-style Web Editor — Auth + Project Storage backend.
// Endpoints:
//   POST /api/auth/register   { username, password } -> { token, user }
//   POST /api/auth/login      { username, password } -> { token, user }
//   GET  /api/auth/me                              -> { user }
//   POST /api/auth/logout                          -> { ok }
//   GET  /api/projects                             -> [{ id, name, clipsCount, updatedAt }]
//   POST /api/projects  { name }                  -> { id }
//   GET  /api/projects/:id                         -> { project, assetsMeta }
//   PUT  /api/projects/:id  { project }           -> { ok }
//   DELETE /api/projects/:id                       -> { ok }
//   GET  /api/projects/:id/assets/:assetId        -> binary
//   PUT  /api/projects/:id/assets/:assetId  BINARY-> { size, kind }
//   DELETE /api/projects/:id/assets/:assetId       -> { ok }
//
// Storage layout:
//   /opt/capcut-auth/data.db        (SQLite)
//   /opt/capcut-auth/users/{id}/     per-user directory
//     projects.json                  metadata
//     projects/{id}.json             track/clips/etc
//     assets/{projectId}/{assetId}.{ext}  binary blobs

'use strict';
const express = require('express');
const cors = require('cors');
const path = require('path');
const fs = require('fs');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const Database = require('better-sqlite3');
const multer = require('multer');

const PORT = process.env.PORT || 3019;
const DATA_DIR = process.env.DATA_DIR || '/opt/capcut-auth';
const USERS_DIR = path.join(DATA_DIR, 'users');
const DB_PATH = path.join(DATA_DIR, 'data.db');
const JWT_SECRET = process.env.JWT_SECRET || 'sj88-capcut-' + Math.random().toString(36).slice(2, 10);
const JWT_EXPIRES = '30d';

// -----------------------------------------------------------------------
// Setup
// -----------------------------------------------------------------------
fs.mkdirSync(USERS_DIR, { recursive: true });
const db = new Database(DB_PATH);
db.pragma('journal_mode = WAL');
db.exec(`
  CREATE TABLE IF NOT EXISTS users (
    id TEXT PRIMARY KEY,
    username TEXT UNIQUE NOT NULL,
    password_hash TEXT NOT NULL,
    display_name TEXT,
    created_at INTEGER NOT NULL
  );
  CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
`);

const app = express();
app.use(cors({
  origin: (origin, cb) => cb(null, true), // CapCut Web on :3000, allow all
  credentials: true,
}));
app.use(express.json({ limit: '50mb' }));

// -----------------------------------------------------------------------
// Auth middleware
// -----------------------------------------------------------------------
function authOptional(req, res, next) {
  const h = req.headers.authorization || '';
  const m = h.match(/^Bearer (.+)$/);
  if (m) {
    try { req.user = jwt.verify(m[1], JWT_SECRET); } catch {}
  }
  next();
}
function authRequired(req, res, next) {
  if (!req.user) return res.status(401).json({ error: 'ต้อง login ก่อน' });
  next();
}

// -----------------------------------------------------------------------
// Helpers
// -----------------------------------------------------------------------
function uid() {
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
}
function safeUsername(s) {
  if (typeof s !== 'string') return null;
  const trimmed = s.trim();
  if (!/^[a-zA-Z0-9_]{3,32}$/.test(trimmed)) return null;
  return trimmed;
}
function userDir(userId) {
  return path.join(USERS_DIR, userId);
}
function projectDir(userId, projectId) {
  return path.join(userDir(userId), 'projects', `${projectId}.json`);
}
function assetsDir(userId, projectId) {
  return path.join(userDir(userId), 'assets', projectId);
}
function projectsMetaPath(userId) {
  return path.join(userDir(userId), 'projects.json');
}
function readProjectsMeta(userId) {
  const p = projectsMetaPath(userId);
  if (!fs.existsSync(p)) return [];
  try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return []; }
}
function writeProjectsMeta(userId, list) {
  fs.writeFileSync(projectsMetaPath(userId), JSON.stringify(list, null, 2));
}
function bumpProject(userId, projectId, project) {
  const list = readProjectsMeta(userId);
  const idx = list.findIndex((p) => p.id === projectId);
  const entry = {
    id: projectId,
    name: project?.name ?? project?.projectName ?? (idx >= 0 ? list[idx].name : 'ไม่มีชื่อ'),
    clipsCount: (project?.clips ?? project?.project?.clips ?? []).length,
    updatedAt: Date.now(),
  };
  if (idx >= 0) list[idx] = entry;
  else list.unshift(entry);
  writeProjectsMeta(userId, list);
  return entry;
}

// Multer — disk storage, per-project folder
const upload = multer.diskStorage({
  destination(req, file, cb) {
    const dir = assetsDir(req.user.id, req.params.projectId);
    fs.mkdirSync(dir, { recursive: true });
    cb(null, dir);
  },
  filename(req, file, cb) {
    // Use original filename with sanitized variant — use assetId from header
    const assetId = req.headers['x-asset-id'] || file.fieldname || uid();
    const ext = path.extname(file.originalname) || '';
    cb(null, `${assetId}${ext}`);
  },
});
const uploadMw = multer({ storage: upload, limits: { fileSize: 500 * 1024 * 1024 } });

// -----------------------------------------------------------------------
// Auth routes
// -----------------------------------------------------------------------
app.post('/api/auth/register', (req, res) => {
  const { username, password } = req.body || {};
  const u = safeUsername(username);
  if (!u) return res.status(400).json({ error: 'ชื่อผู้ใช้ 3-32 ตัว (a-z, A-Z, 0-9, _)' });
  if (!password || password.length < 6) {
    return res.status(400).json({ error: 'รหัสผ่านต้องมากกว่า 6 ตัวอักษร' });
  }
  const existing = db.prepare('SELECT id FROM users WHERE username=?').get(u);
  if (existing) return res.status(409).json({ error: 'ชื่อผู้ใช้นี้ถูกใช้แล้ว' });
  const id = uid();
  const hash = bcrypt.hashSync(password, 10);
  db.prepare(
    'INSERT INTO users (id, username, password_hash, display_name, created_at) VALUES (?,?,?,?,?)',
  ).run(id, u, hash, u, Date.now());
  fs.mkdirSync(userDir(id), { recursive: true });
  fs.writeFileSync(projectsMetaPath(id), '[]');
  const token = jwt.sign({ id, username: u }, JWT_SECRET, { expiresIn: JWT_EXPIRES });
  res.json({ token, user: { id, username: u, displayName: u, createdAt: Date.now() } });
});

app.post('/api/auth/login', (req, res) => {
  const { username, password } = req.body || {};
  const u = safeUsername(username);
  if (!u) return res.status(400).json({ error: 'ชื่อผู้ใช้ไม่ถูกต้อง' });
  const row = db.prepare('SELECT * FROM users WHERE username=?').get(u);
  if (!row) return res.status(401).json({ error: 'ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง' });
  if (!bcrypt.compareSync(password, row.password_hash)) {
    return res.status(401).json({ error: 'ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง' });
  }
  const token = jwt.sign({ id: row.id, username: row.username }, JWT_SECRET, {
    expiresIn: JWT_EXPIRES,
  });
  res.json({
    token,
    user: {
      id: row.id,
      username: row.username,
      displayName: row.display_name,
      createdAt: row.created_at,
    },
  });
});

app.get('/api/auth/me', authOptional, authRequired, (req, res) => {
  const row = db.prepare('SELECT id, username, display_name, created_at FROM users WHERE id=?').get(req.user.id);
  if (!row) return res.status(404).json({ error: 'ไม่พบผู้ใช้' });
  res.json({ user: { id: row.id, username: row.username, displayName: row.display_name, createdAt: row.created_at } });
});

app.post('/api/auth/logout', (req, res) => res.json({ ok: true }));

// -----------------------------------------------------------------------
// Projects
// -----------------------------------------------------------------------
app.get('/api/projects', authOptional, authRequired, (req, res) => {
  const list = readProjectsMeta(req.user.id);
  res.json({ projects: list });
});

app.post('/api/projects', authOptional, authRequired, (req, res) => {
  const { name, project } = req.body || {};
  if (!name || typeof name !== 'string') {
    return res.status(400).json({ error: 'ต้องระบุชื่อโปรเจกต์ (name)' });
  }
  const id = uid();
  const projPath = projectDir(req.user.id, id);
  fs.mkdirSync(path.dirname(projPath), { recursive: true });
  fs.writeFileSync(projPath, JSON.stringify(project ?? { name }, null, 2));
  const entry = bumpProject(req.user.id, id, { name, clips: project?.clips ?? [] });
  res.json({ project: entry });
});

app.get('/api/projects/:id', authOptional, authRequired, (req, res) => {
  const projPath = projectDir(req.user.id, req.params.id);
  if (!fs.existsSync(projPath)) return res.status(404).json({ error: 'ไม่พบโปรเจกต์' });
  let project;
  try { project = JSON.parse(fs.readFileSync(projPath, 'utf8')); } catch { project = null; }
  // list assets for this project
  const assetsDir_ = assetsDir(req.user.id, req.params.id);
  const assetsMeta = [];
  if (fs.existsSync(assetsDir_)) {
    for (const f of fs.readdirSync(assetsDir_)) {
      const stat = fs.statSync(path.join(assetsDir_, f));
      assetsMeta.push({ id: path.basename(f, path.extname(f)), size: stat.size });
    }
  }
  res.json({ project, assetsMeta });
});

app.put('/api/projects/:id', authOptional, authRequired, (req, res) => {
  const projPath = projectDir(req.user.id, req.params.id);
  if (!fs.existsSync(projPath)) return res.status(404).json({ error: 'ไม่พบโปรเจกต์' });
  const { project } = req.body || {};
  if (!project) return res.status(400).json({ error: 'ต้องส่ง project' });
  fs.writeFileSync(projPath, JSON.stringify(project, null, 2));
  bumpProject(req.user.id, req.params.id, project);
  res.json({ ok: true });
});

app.delete('/api/projects/:id', authOptional, authRequired, (req, res) => {
  const projPath = projectDir(req.user.id, req.params.id);
  if (fs.existsSync(projPath)) fs.unlinkSync(projPath);
  const assetsDir_ = assetsDir(req.user.id, req.params.id);
  if (fs.existsSync(assetsDir_)) fs.rmSync(assetsDir_, { recursive: true, force: true });
  const list = readProjectsMeta(req.user.id).filter((p) => p.id !== req.params.id);
  writeProjectsMeta(req.user.id, list);
  res.json({ ok: true });
});

// -----------------------------------------------------------------------
// Assets (binary blobs — video/audio/image for each project)
// -----------------------------------------------------------------------
app.put(
  '/api/projects/:projectId/assets/:assetId',
  authOptional,
  authRequired,
  uploadMw.single('file'),
  (req, res) => {
    if (!req.file) return res.status(400).json({ error: 'ไม่มีไฟล์' });
    const stat = fs.statSync(req.file.path);
    res.json({ ok: true, size: stat.size, path: req.file.path });
  },
);

app.get('/api/projects/:projectId/assets/:assetId', authOptional, authRequired, (req, res) => {
  const dir = assetsDir(req.user.id, req.params.projectId);
  if (!fs.existsSync(dir)) return res.status(404).json({ error: 'ไม่พบไฟล์' });
  // Match by basename without extension
  for (const f of fs.readdirSync(dir)) {
    if (path.basename(f, path.extname(f)) === req.params.assetId) {
      return res.sendFile(path.join(dir, f));
    }
  }
  res.status(404).json({ error: 'ไม่พบไฟล์' });
});

app.delete(
  '/api/projects/:projectId/assets/:assetId',
  authOptional,
  authRequired,
  (req, res) => {
    const dir = assetsDir(req.user.id, req.params.projectId);
    if (fs.existsSync(dir)) {
      for (const f of fs.readdirSync(dir)) {
        if (path.basename(f, path.extname(f)) === req.params.assetId) {
          fs.unlinkSync(path.join(dir, f));
          return res.json({ ok: true });
        }
      }
    }
    res.status(404).json({ error: 'ไม่พบไฟล์' });
  },
);

// -----------------------------------------------------------------------
// Health + root
// -----------------------------------------------------------------------
app.get('/api/health', (req, res) =>
  res.json({ ok: true, service: 'capcut-auth', time: Date.now() }),
);
app.get('/', (req, res) => res.send('CapCut-Auth API — see /api/health'));

app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).json({ error: err.message || 'server error' });
});

app.listen(PORT, '0.0.0.0', () => {
  console.log(`[capcut-auth] listening on http://127.0.0.1:${PORT} (data: ${DATA_DIR})`);
});
