/**
 * Routes de monitoring (admin)
 * GET /admin/monitoring
 *
 * Objectif: fournir des informations "réelles" pour diagnostiquer rapidement l'état
 * de l'API, de la BDD et du stockage privé.
 */
import type { FastifyInstance } from "fastify";
import type { RouteContext } from "./types.js";
import { access, readdir } from "node:fs/promises";
import { constants as fsConstants } from "node:fs";

type CheckResult = {
  ok: boolean;
  latencyMs?: number;
  error?: string;
  meta?: Record<string, unknown>;
};

const toMb = (bytes: number) => Math.round((bytes / (1024 * 1024)) * 10) / 10;

const measure = async <T>(fn: () => Promise<T>) => {
  const start = Date.now();
  const result = await fn();
  return { result, latencyMs: Date.now() - start };
};

const safeAccessDir = async (path: string): Promise<CheckResult> => {
  try {
    await access(path, fsConstants.R_OK);
    const entries = await readdir(path, { withFileTypes: true });
    const dirs = entries.filter((e) => e.isDirectory()).length;
    const files = entries.filter((e) => e.isFile()).length;
    return { ok: true, meta: { path, entries: entries.length, dirs, files } };
  } catch (error) {
    return {
      ok: false,
      error: error instanceof Error ? error.message : "inaccessible",
      meta: { path },
    };
  }
};

export const registerAdminMonitoringRoutes = async (
  app: FastifyInstance,
  ctx: RouteContext,
): Promise<void> => {
  const { db, config, packageVersion, requireAdmin } = ctx;

  app.get(
    "/admin/monitoring",
    { preHandler: requireAdmin },
    async (_request, reply) => {
      const nowIso = new Date().toISOString();
      const mem = process.memoryUsage();

      const api = {
        name: "rollerlogic-api",
        env: config.appEnv,
        version: packageVersion,
        timestamp: nowIso,
        uptimeSec: Math.floor(process.uptime()),
        node: process.version,
        pid: process.pid,
        memoryMb: {
          rss: toMb(mem.rss),
          heapUsed: toMb(mem.heapUsed),
          heapTotal: toMb(mem.heapTotal),
        },
      };

      // ── Database ──────────────────────────────────────────────────────────────
      let dbCheck: CheckResult = { ok: false, error: "not_checked" };
      let tablesCheck: CheckResult = { ok: false, error: "not_checked" };
      let configCheck: CheckResult = { ok: false, error: "not_checked" };
      let metrics: Record<string, unknown> = {};
      let recentErrors: Array<Record<string, unknown>> = [];

      try {
        const ping = await measure(async () => {
          await db.query("SELECT 1");
        });
        dbCheck = { ok: true, latencyMs: ping.latencyMs };

        const expectedTables = [
          "users",
          "wallets",
          "progress",
          "user_stats",
          "app_config",
          "audit_log",
          "leaderboard_entries",
          "payment_transactions",
        ];
        const { result: tableRows, latencyMs: tablesLatency } = await measure(
          async () =>
            await db.query(
              "SELECT table_name as name FROM information_schema.tables WHERE table_schema = DATABASE()",
            ),
        );
        const rows = (tableRows as unknown as [unknown, unknown])[0];
        const tableNames = new Set<string>();
        if (Array.isArray(rows)) {
          for (const row of rows) {
            const name = (row as { name?: unknown }).name;
            if (typeof name === "string" && name) tableNames.add(name);
          }
        }
        const missingTables = expectedTables.filter((t) => !tableNames.has(t));
        tablesCheck = {
          ok: missingTables.length === 0,
          latencyMs: tablesLatency,
          meta: { expected: expectedTables.length, missing: missingTables },
        };

        // app_config (maintenance / launch_date)
        try {
          const { result: configRows, latencyMs: cfgLatency } = await measure(
            async () =>
              await db.query(
                "SELECT config_key, config_value FROM app_config WHERE config_key IN ('maintenance_mode', 'maintenance_message', 'maintenance_end', 'launch_date')",
              ),
          );
          const cfg = (configRows as unknown as [unknown, unknown])[0];
          const raw: Record<string, string> = {};
          if (Array.isArray(cfg)) {
            for (const item of cfg) {
              const key = (item as { config_key?: unknown }).config_key;
              const val = (item as { config_value?: unknown }).config_value;
              if (typeof key === "string") {
                raw[key] = typeof val === "string" ? val : String(val ?? "");
              }
            }
          }
          const maintenanceRaw = (raw.maintenance_mode ?? "").trim();
          const maintenance =
            maintenanceRaw === "1" || maintenanceRaw.toLowerCase() === "true";
          const launchDate = (raw.launch_date ?? "").trim();
          configCheck = {
            ok: true,
            latencyMs: cfgLatency,
            meta: {
              maintenance,
              maintenanceEnd: raw.maintenance_end ?? null,
              maintenanceMessage: maintenance ? (raw.maintenance_message ?? "") : null,
              launchDate: launchDate && launchDate !== "unset" ? launchDate : null,
            },
          };
        } catch (error) {
          configCheck = {
            ok: false,
            error: error instanceof Error ? error.message : "config_query_failed",
          };
        }

        // Metrics (best-effort)
        try {
          const [usersRows] = await db.query(
            "SELECT COUNT(*) as total, SUM(CASE WHEN guest = 1 THEN 1 ELSE 0 END) as guests FROM users",
          );
          const totalUsers =
            Array.isArray(usersRows) && usersRows[0]
              ? Number((usersRows[0] as { total?: unknown }).total) || 0
              : 0;
          const guestUsers =
            Array.isArray(usersRows) && usersRows[0]
              ? Number((usersRows[0] as { guests?: unknown }).guests) || 0
              : 0;

          const [newTodayRows] = await db.query(
            "SELECT COUNT(*) as total FROM users WHERE DATE(created_at) = CURDATE()",
          );
          const newUsersToday =
            Array.isArray(newTodayRows) && newTodayRows[0]
              ? Number((newTodayRows[0] as { total?: unknown }).total) || 0
              : 0;

          const [errorsHourRows] = await db.query(
            "SELECT COUNT(*) as total FROM audit_log WHERE created_at >= DATE_SUB(NOW(), INTERVAL 1 HOUR) AND (action LIKE '%error%' OR action LIKE '%fail%' OR action LIKE '%denied%')",
          );
          const errorsLastHour =
            Array.isArray(errorsHourRows) && errorsHourRows[0]
              ? Number((errorsHourRows[0] as { total?: unknown }).total) || 0
              : 0;

          metrics = {
            usersTotal: totalUsers,
            usersGuests: guestUsers,
            usersRegistered: Math.max(0, totalUsers - guestUsers),
            newUsersToday,
            auditErrorsLastHour: errorsLastHour,
          };
        } catch {
          // ignore metrics failures
        }

        // Recent errors for quick triage (best-effort)
        try {
          const [errorRows] = await db.query(
            `SELECT id, user_id as userId, action, ip, created_at as createdAt
             FROM audit_log
             WHERE (action LIKE '%error%' OR action LIKE '%fail%' OR action LIKE '%denied%')
             ORDER BY created_at DESC
             LIMIT 8`,
          );
          recentErrors = Array.isArray(errorRows)
            ? errorRows.map((r) => r as Record<string, unknown>)
            : [];
        } catch {
          // ignore
        }
      } catch (error) {
        dbCheck = {
          ok: false,
          error: error instanceof Error ? error.message : "db_unreachable",
        };
      }

      // ── Storage ───────────────────────────────────────────────────────────────
      const storagePaths = {
        privateRoot: "/app/private",
        avatars: "/app/private/avatars",
        ballSkins: "/app/private/ball-skins",
        betaTester: "/app/private/beta_testeur",
      } as const;

      const storage = {
        privateRoot: await safeAccessDir(storagePaths.privateRoot),
        avatars: await safeAccessDir(storagePaths.avatars),
        ballSkins: await safeAccessDir(storagePaths.ballSkins),
        betaTester: await safeAccessDir(storagePaths.betaTester),
      };

      const status =
        dbCheck.ok && storage.privateRoot.ok && tablesCheck.ok
          ? "ok"
          : "degraded";

      return reply.send({
        status,
        api,
        checks: {
          database: dbCheck,
          schema: tablesCheck,
          appConfig: configCheck,
          storage,
        },
        metrics,
        recentErrors,
      });
    },
  );
};

