// Storage admin endpoints — unified diagnostics for the growing-files mount
// and the S3 object-storage bucket. Read-only; the actual settings editors
// continue to live under /settings/s3 and /settings/growing.

import express from 'express';
import fs from 'node:fs';
import { promisify } from 'node:util';
import { exec as execCb } from 'node:child_process';
import { HeadBucketCommand, ListObjectsV2Command } from '@aws-sdk/client-s3';
import pool from '../db/pool.js';
import { s3Client, getS3Bucket } from '../s3/client.js';

const exec = promisify(execCb);
const router = express.Router();

// Defaults mirrored from settings.js so the overview never returns nulls.
// Growing-file mode is now per-recorder; "enabled" here means the shared SMB
// landing zone is CONFIGURED (a mount source is set), not a global on/off.
const GROWING_DEFAULTS = {
  growing_path: '/growing',
  growing_smb_url: '',
  growing_smb_mount: '',
  growing_promote_after_seconds: '8',
};

async function readSettings(keys) {
  const result = await pool.query(
    `SELECT key, value FROM settings WHERE key = ANY($1)`,
    [keys]
  );
  const out = {};
  for (const { key, value } of result.rows) out[key] = value;
  return out;
}

// Probe a filesystem path: does it exist, is it writable, how much free space.
// All checks are best-effort — any failure becomes { ok: false, error }.
async function probeGrowingPath(path) {
  const result = { path, exists: false, writable: false, free_bytes: null, total_bytes: null, error: null };
  if (!path) { result.error = 'no path configured'; return result; }

  try {
    const stat = fs.statSync(path);
    result.exists = stat.isDirectory();
    if (!result.exists) { result.error = 'path is not a directory'; return result; }
  } catch (err) {
    result.error = err.code === 'ENOENT' ? 'path does not exist' : err.message;
    return result;
  }

  try {
    fs.accessSync(path, fs.constants.W_OK);
    result.writable = true;
  } catch (err) {
    result.error = 'not writable: ' + err.message;
  }

  // df -P returns POSIX-portable output: "Filesystem 1024-blocks Used Available Capacity Mounted-on"
  try {
    const { stdout } = await exec(`df -PB1 ${JSON.stringify(path)}`, { timeout: 3000 });
    const lines = stdout.trim().split('\n');
    if (lines.length >= 2) {
      const cols = lines[1].split(/\s+/);
      // cols: [fs, total, used, available, capacity, mountpoint]
      result.total_bytes = parseInt(cols[1], 10) || null;
      result.free_bytes  = parseInt(cols[3], 10) || null;
    }
  } catch (_err) {
    // df not available or path inaccessible — leave free_bytes null.
  }

  return result;
}

async function probeS3Bucket() {
  const bucket = getS3Bucket();
  const out = { bucket, reachable: false, head_latency_ms: null, method: null, error: null };
  if (!bucket) { out.error = 'no bucket configured'; return out; }

  const started = Date.now();
  try {
    await s3Client.send(new HeadBucketCommand({ Bucket: bucket }));
    out.reachable = true;
    out.method = 'HeadBucket';
  } catch (headErr) {
    // Fall back to a 0-key list for stores that don't expose HeadBucket.
    try {
      await s3Client.send(new ListObjectsV2Command({ Bucket: bucket, MaxKeys: 0 }));
      out.reachable = true;
      out.method = 'ListObjectsV2';
    } catch (listErr) {
      out.error = listErr.message || headErr.message;
    }
  }
  out.head_latency_ms = Date.now() - started;
  return out;
}

// GET /api/v1/storage/overview
// Consolidated read-only view of the storage subsystem for the admin UI.
router.get('/overview', async (req, res, next) => {
  try {
    // Growing files — merge defaults with whatever's in `settings`.
    const growingRaw = { ...GROWING_DEFAULTS, ...(await readSettings(Object.keys(GROWING_DEFAULTS))) };
    // "enabled" now means the shared SMB landing zone is configured (a mount
    // source is set). Per-recorder toggles decide which recorders actually use it.
    const growingEnabled = !!(growingRaw.growing_smb_mount && growingRaw.growing_smb_mount.trim());
    const containerPath = growingRaw.growing_path || '/growing';
    const mount = await probeGrowingPath(containerPath);

    // S3 — bucket name comes from the live client (env or DB-loaded), not
    // a fresh DB read, so we report exactly what the running client uses.
    const s3 = await probeS3Bucket();
    const s3SettingsRaw = await readSettings(['s3_endpoint', 's3_region']);

    res.json({
      growing: {
        enabled: growingEnabled,
        container_path: containerPath,
        // host_path isn't authoritatively known to the API container, but the
        // existing deploy uses this symlink — surface it for operator context.
        host_path: '/mnt/NVME/MAM/wild-dragon-growing',
        smb_url: growingRaw.growing_smb_url || '',
        smb_mount: growingRaw.growing_smb_mount || '',
        promote_after_seconds: parseInt(growingRaw.growing_promote_after_seconds, 10) || 8,
        exists: mount.exists,
        writable: mount.writable,
        free_bytes: mount.free_bytes,
        total_bytes: mount.total_bytes,
        error: mount.error,
      },
      s3: {
        endpoint: s3SettingsRaw.s3_endpoint || process.env.S3_ENDPOINT || '',
        bucket: s3.bucket,
        region: s3SettingsRaw.s3_region || process.env.S3_REGION || 'us-east-1',
        reachable: s3.reachable,
        head_latency_ms: s3.head_latency_ms,
        probe_method: s3.method,
        error: s3.error,
      },
    });
  } catch (err) {
    next(err);
  }
});

export default router;
