import { createRouter } from '../../create-router';
import { getMusicServerUrl } from './sidecar-server';
import {
  getMusicFavorites,
  addMusicFavorite,
  removeMusicFavorite,
  getNowPlaying,
  setNowPlaying,
  clearNowPlaying,
  type FavoriteKind,
} from 'officerdb';

// Thin reverse-proxy for /api/music/*. Auth is handled upstream by userMiddleware (this router mounts
// under the protected /api tree, so the media `?token=` path works). Everything else — path resolution,
// byte-range streaming, ffprobe duration, indexing — is done by the officer-music sidecar's audio
// server. We only forward the subpath + query + Range and stream the response back.
//
// This is a catch-all, so it lists no routes: the full /api/music/* HTTP contract (stream, manifest,
// meta, cover, reindex, reindex/stream + their SSE/response shapes) is documented at the top of the
// sidecar's fetch handler — src/servers/sidecar/music/index.ts.

export const musicRouter = createRouter();

// ── Per-user music state (favorites + currently-playing) ─────────────────────────────────────────
// These are USER data, not library data, so the platform serves them from Postgres directly — they are
// NOT proxied to the sidecar (which is stateless about users). Registered before the catch-all proxy
// below so they win; still under /api/music, so the music-app account gate permits them.

const FAVORITE_KINDS = new Set<FavoriteKind>(['track', 'album', 'artist']);
const isKind = (k: unknown): k is FavoriteKind => typeof k === 'string' && FAVORITE_KINDS.has(k as FavoriteKind);

// GET /favorites → { tracks, albums, artists } (arrays of keys, newest first).
musicRouter.get('/favorites', async (ctx) => {
  return ctx.json(await getMusicFavorites(ctx.get('user').id));
});

// POST /favorites { kind, key } → add (idempotent).
musicRouter.post('/favorites', async (ctx) => {
  const { kind, key } = (await ctx.req.json().catch(() => ({}))) as { kind?: unknown; key?: unknown };
  if (!isKind(kind) || typeof key !== 'string' || !key) return ctx.json({ error: 'kind and key required' }, 400);
  await addMusicFavorite(ctx.get('user').id, kind, key);
  return ctx.json({ ok: true });
});

// DELETE /favorites?kind=&key= → remove (query params so any HTTP client can send it).
musicRouter.delete('/favorites', async (ctx) => {
  const kind = ctx.req.query('kind');
  const key = ctx.req.query('key');
  if (!isKind(kind) || !key) return ctx.json({ error: 'kind and key required' }, 400);
  await removeMusicFavorite(ctx.get('user').id, kind, key);
  return ctx.json({ ok: true });
});

// GET /now-playing → the snapshot, or null.
musicRouter.get('/now-playing', async (ctx) => {
  return ctx.json(await getNowPlaying(ctx.get('user').id));
});

// PUT /now-playing { homePath, dir?, title?, artist?, album?, durationSec?, positionSec? } → upsert.
musicRouter.put('/now-playing', async (ctx) => {
  const b = (await ctx.req.json().catch(() => ({}))) as Record<string, unknown>;
  if (typeof b.homePath !== 'string' || !b.homePath) return ctx.json({ error: 'homePath required' }, 400);
  const str = (v: unknown) => (typeof v === 'string' ? v : undefined);
  const num = (v: unknown) => (typeof v === 'number' && Number.isFinite(v) ? v : undefined);
  await setNowPlaying(ctx.get('user').id, {
    homePath: b.homePath,
    dir: str(b.dir),
    title: str(b.title),
    artist: str(b.artist),
    album: str(b.album),
    durationSec: num(b.durationSec),
    positionSec: num(b.positionSec),
  });
  return ctx.json({ ok: true });
});

// DELETE /now-playing → clear.
musicRouter.delete('/now-playing', async (ctx) => {
  await clearNowPlaying(ctx.get('user').id);
  return ctx.json({ ok: true });
});

const PREFIX = '/api/music';

musicRouter.all('/*', async (ctx) => {
  const baseUrl = getMusicServerUrl();
  if (!baseUrl) return ctx.text('Music sidecar not available', 503);

  const url = new URL(ctx.req.url);
  const subpath = url.pathname.slice(PREFIX.length) || '/';
  const target = `${baseUrl}${subpath}${url.search}`;

  // A from-scratch reindex holds this proxied connection open for minutes with no bytes flowing, which
  // the main server's 60s idle timeout would drop. Extend it to 30 min for the build/progress endpoints
  // (Bun passes the server as Hono's env). Matches the sidecar's own per-request extension.
  if (subpath === '/reindex' || subpath === '/reindex/stream') {
    const server = ctx.env as { timeout?: (req: Request, seconds: number) => void } | undefined;
    try {
      server?.timeout?.(ctx.req.raw, 1800);
    } catch {
      /* older Bun / no per-request timeout — the build still completes in the background */
    }
  }

  const range = ctx.req.header('range');
  let upstream: Response;
  try {
    upstream = await fetch(target, {
      method: ctx.req.method,
      headers: range ? { Range: range } : {},
    });
  } catch (err) {
    console.error('[music] proxy fetch failed', { target, error: String(err) });
    return ctx.text('Music sidecar unreachable', 502);
  }

  // Pass status + headers through and stream the body (206/Content-Range/X-Audio-Duration included).
  return new Response(upstream.body, { status: upstream.status, headers: new Headers(upstream.headers) });
});
