/* ============================================================================
 * db.js — libsql (embedded SQLite) database setup and query helpers
 *
 * The DB file lives at $DB_PATH (mounted volume in Docker so data persists
 * across container rebuilds). libsql is an open-source SQLite fork by Turso
 * with WAL-by-default for concurrent reads.
 *
 * Schema (three small tables):
 *   users         — one row per fingerprint; tracks last app version seen
 *   playlists     — one row per fingerprint; full playlist JSON blob
 *   video_history — one row per (fingerprint, video_id); recent 200 entries
 * ========================================================================== */

import { createClient } from '@libsql/client';
import { join } from 'node:path';

const DB_PATH = process.env.DB_PATH || join(process.cwd(), 'data', 'ytplayer.db');

export const db = createClient({
  url: 'file:' + DB_PATH,
});

// ---- Schema ----------------------------------------------------------------

export async function initDb() {
  await db.executeMultiple(`
    PRAGMA journal_mode = WAL;
    PRAGMA synchronous = NORMAL;
    PRAGMA foreign_keys = ON;

    CREATE TABLE IF NOT EXISTS users (
      fingerprint  TEXT    PRIMARY KEY,
      last_version TEXT,
      last_seen    INTEGER NOT NULL DEFAULT (unixepoch())
    );

    CREATE TABLE IF NOT EXISTS playlists (
      fingerprint TEXT    PRIMARY KEY,
      data        TEXT    NOT NULL DEFAULT '[]',
      updated_at  INTEGER NOT NULL DEFAULT (unixepoch())
    );

    CREATE TABLE IF NOT EXISTS video_history (
      fingerprint TEXT    NOT NULL,
      video_id    TEXT    NOT NULL,
      title       TEXT,
      channel     TEXT,
      thumbnail   TEXT,
      duration    REAL    DEFAULT 0,
      accessed_at INTEGER NOT NULL DEFAULT (unixepoch()),
      PRIMARY KEY (fingerprint, video_id)
    );

    CREATE INDEX IF NOT EXISTS idx_vh_fp_time
      ON video_history (fingerprint, accessed_at DESC);

    CREATE TABLE IF NOT EXISTS profiles (
      name       TEXT    PRIMARY KEY,
      data       TEXT    NOT NULL DEFAULT '{}',
      created_at INTEGER NOT NULL DEFAULT (unixepoch()),
      updated_at INTEGER NOT NULL DEFAULT (unixepoch())
    );
  `);
}

// ---- Profiles (named cross-device sync; the name acts as the passkey) ------

// Insert a new profile. Returns false when the name is already taken.
export async function createProfile(name, dataJson) {
  try {
    await db.execute({
      sql: `INSERT INTO profiles (name, data, created_at, updated_at)
            VALUES (?, ?, unixepoch(), unixepoch())`,
      args: [name, dataJson],
    });
    return true;
  } catch (err) {
    const msg = String(err && err.message || err);
    if (msg.includes('UNIQUE') || msg.includes('PRIMARY KEY')) return false;
    throw err;
  }
}

export async function getProfile(name) {
  const r = await db.execute({
    sql: 'SELECT data, updated_at FROM profiles WHERE name = ?',
    args: [name],
  });
  const row = r.rows[0];
  if (!row) return null;
  return { data: row.data, updatedAt: Number(row.updated_at) };
}

// Update an EXISTING profile's data blob. Returns false when it doesn't exist
// (saving must never implicitly create a profile — creation is a deliberate,
// uniqueness-checked act).
export async function saveProfile(name, dataJson) {
  const r = await db.execute({
    sql: 'UPDATE profiles SET data = ?, updated_at = unixepoch() WHERE name = ?',
    args: [dataJson, name],
  });
  return (r.rowsAffected || 0) > 0;
}

// ---- Helpers ---------------------------------------------------------------

// Upsert the users row and optionally update playlists.
export async function upsertUser({ fingerprint, appVersion, playlists }) {
  await db.execute({
    sql: `INSERT INTO users (fingerprint, last_version, last_seen)
          VALUES (?, ?, unixepoch())
          ON CONFLICT(fingerprint) DO UPDATE SET
            last_version = excluded.last_version,
            last_seen    = excluded.last_seen`,
    args: [fingerprint, appVersion || null],
  });

  if (Array.isArray(playlists)) {
    await db.execute({
      sql: `INSERT INTO playlists (fingerprint, data, updated_at)
            VALUES (?, ?, unixepoch())
            ON CONFLICT(fingerprint) DO UPDATE SET
              data       = excluded.data,
              updated_at = excluded.updated_at`,
      args: [fingerprint, JSON.stringify(playlists)],
    });
  }
}

// Record a video access (insert or bump accessed_at).
export async function recordVideoAccess(fingerprint, video) {
  await db.execute({
    sql: `INSERT INTO video_history
            (fingerprint, video_id, title, channel, thumbnail, duration, accessed_at)
          VALUES (?, ?, ?, ?, ?, ?, unixepoch())
          ON CONFLICT(fingerprint, video_id) DO UPDATE SET
            title       = excluded.title,
            channel     = excluded.channel,
            thumbnail   = excluded.thumbnail,
            duration    = excluded.duration,
            accessed_at = excluded.accessed_at`,
    args: [
      fingerprint,
      video.id,
      video.title || null,
      video.channel || null,
      video.thumbnail || null,
      video.duration || 0,
    ],
  });

  // Keep only the 200 most recent entries per user to avoid unbounded growth
  await db.execute({
    sql: `DELETE FROM video_history
          WHERE fingerprint = ?
            AND video_id NOT IN (
              SELECT video_id FROM video_history
              WHERE fingerprint = ?
              ORDER BY accessed_at DESC
              LIMIT 200
            )`,
    args: [fingerprint, fingerprint],
  });
}

// Fetch stored data for a fingerprint.
export async function getUserData(fingerprint) {
  const [userRow, plRow, histRows] = await Promise.all([
    db.execute({ sql: 'SELECT last_version FROM users WHERE fingerprint = ?', args: [fingerprint] }),
    db.execute({ sql: 'SELECT data FROM playlists WHERE fingerprint = ?', args: [fingerprint] }),
    db.execute({
      sql: `SELECT video_id AS id, title, channel, thumbnail, duration, accessed_at
            FROM video_history WHERE fingerprint = ?
            ORDER BY accessed_at DESC LIMIT 50`,
      args: [fingerprint],
    }),
  ]);

  const lastVersion = userRow.rows[0]?.last_version || null;
  const playlists   = plRow.rows[0]?.data ? JSON.parse(plRow.rows[0].data) : [];
  const history     = histRows.rows.map((r) => ({
    id: r.id, title: r.title, channel: r.channel,
    thumbnail: r.thumbnail, duration: r.duration,
  }));

  return { lastVersion, playlists, history };
}
