//! `GET /api/config/status` — which source governs this process's
//! configuration, and whether a change already written needs a restart.
//!
//! One home for both facts. `authority`, `path` and `reconciled_at` are the
//! invocation's answer (milestone-20 ruling 1): the database carries no record
//! of who wrote it, so only the running process can say. `restart_pending` is
//! the running process's own memory of a mutation whose effect waits for the
//! next start — the upstream pool and the scalar settings are both built at
//! startup. Nothing clears it but process exit, so a client that sees it false
//! after a restart is seeing the truth rather than a cleared flag.
//!
//! The route needs a session: `path` is a filesystem path and must never reach
//! the open `/api/version` or `/api/health`.

const std = @import("std");

const http_util = @import("../http_util.zig");
const server = @import("../server.zig");

const Request = http_util.Request;
const HandlerError = http_util.HandlerError;

pub const View = struct {
    authority: []const u8,
    /// The managed file, or null in database mode.
    path: ?[]const u8,
    /// When this process loaded the managed file, in epoch seconds; null in
    /// database mode, which never reconciles. It means exactly that: a file
    /// whose mtime is newer has not been loaded by the running process.
    reconciled_at: ?i64,
    restart_pending: bool,
};

pub fn view(state: *const server.WebState) View {
    return switch (state.authority) {
        .database => .{
            .authority = "database",
            .path = null,
            .reconciled_at = state.reconciled_at,
            .restart_pending = state.restart_pending.load(.monotonic),
        },
        .managed_file => |path| .{
            .authority = "managed_file",
            .path = path,
            .reconciled_at = state.reconciled_at,
            .restart_pending = state.restart_pending.load(.monotonic),
        },
    };
}

pub fn get(state: *server.WebState, io: std.Io, request: *Request) HandlerError!void {
    _ = io;
    return http_util.respondJson(request, .ok, view(state), &.{});
}

// ---------------------------------------------------------------------------
// tests
// ---------------------------------------------------------------------------

const testing = std.testing;

test "database authority reports no file and no reconcile time" {
    var state: server.WebState = .{ .gpa = testing.allocator };
    const current = view(&state);
    try testing.expectEqualStrings("database", current.authority);
    try testing.expectEqual(@as(?[]const u8, null), current.path);
    try testing.expectEqual(@as(?i64, null), current.reconciled_at);
    try testing.expect(!current.restart_pending);
}

test "file authority reports the path and the load time" {
    var state: server.WebState = .{
        .gpa = testing.allocator,
        .authority = .{ .managed_file = "/etc/nxdns/config.zon" },
        .reconciled_at = 1_700_000_042,
    };
    const current = view(&state);
    try testing.expectEqualStrings("managed_file", current.authority);
    try testing.expectEqualStrings("/etc/nxdns/config.zon", current.path.?);
    try testing.expectEqual(@as(?i64, 1_700_000_042), current.reconciled_at);
}

test "the flag the mutations raise is what the view reports" {
    var state: server.WebState = .{ .gpa = testing.allocator };
    try testing.expect(!view(&state).restart_pending);
    state.restart_pending.store(true, .monotonic);
    try testing.expect(view(&state).restart_pending);
}
