/// POST /v1/vault/push — Store a share for a user.
///
/// Expects JSON body: {"identity":"<hex>","share":"<base64>","index":<n>,"version":<n>}
/// Stores the share to disk with HMAC integrity via file_store.
const std = @import("std");
const response = @import("response.zig");
const router = @import("router.zig");
const log = @import("../log.zig");
const file_store = @import("../storage/file_store.zig");
const handler_auth = @import("handler_auth.zig");

/// Maximum request body size (1 MiB). Prevents memory exhaustion from oversized payloads.
const MAX_BODY_SIZE = 1024 * 1024;
/// Maximum decoded share size (512 KiB). Encrypted Shamir shares should be small.
const MAX_SHARE_SIZE = 512 * 1024;
/// Identity hash must be exactly 64 hex chars (SHA-256 output).
const IDENTITY_HEX_LEN = 64;

pub fn handle(writer: anytype, body: []const u8, ctx: *const router.RouteContext, session_token: ?[]const u8) !void {
    if (body.len == 0) {
        return response.badRequest(writer, "empty body");
    }

    // Reject oversized request bodies before parsing
    if (body.len > MAX_BODY_SIZE) {
        return response.badRequest(writer, "request body too large");
    }

    // Require session token when guardian is configured
    if (ctx.guardian) |guardian| {
        const tok = session_token orelse {
            return response.jsonError(writer, 401, "Unauthorized", "session token required");
        };
        if (handler_auth.validateSessionToken(tok, guardian.server_secret) == null) {
            return response.jsonError(writer, 401, "Unauthorized", "invalid session token");
        }
    }

    // Parse JSON
    const PushBody = struct {
        identity: []const u8,
        share: []const u8,
        version: u64,
    };

    const parsed = std.json.parseFromSlice(PushBody, ctx.allocator, body, .{}) catch {
        return response.badRequest(writer, "invalid JSON");
    };
    defer parsed.deinit();

    const identity = parsed.value.identity;
    const share_b64 = parsed.value.share;
    const version = parsed.value.version;

    // Validate identity is exactly 64 hex chars (SHA-256 hash)
    if (identity.len != IDENTITY_HEX_LEN) {
        return response.badRequest(writer, "identity must be exactly 64 hex characters");
    }
    for (identity) |c| {
        if (!std.ascii.isHex(c)) {
            return response.badRequest(writer, "identity must be hex");
        }
    }

    // Decode base64 share
    const decoded_len = std.base64.standard.Decoder.calcSizeForSlice(share_b64) catch {
        return response.badRequest(writer, "invalid base64 in share");
    };

    // Reject oversized shares before allocating
    if (decoded_len > MAX_SHARE_SIZE) {
        return response.badRequest(writer, "share data too large");
    }

    const share_data = ctx.allocator.alloc(u8, decoded_len) catch {
        return response.internalError(writer);
    };
    defer ctx.allocator.free(share_data);

    std.base64.standard.Decoder.decode(share_data, share_b64) catch {
        return response.badRequest(writer, "invalid base64 in share");
    };

    if (share_data.len == 0) {
        return response.badRequest(writer, "share data is empty");
    }

    // Anti-rollback: reject shares with version <= current stored version
    const current_version = readCurrentVersion(ctx.data_dir, identity, ctx.allocator);
    if (current_version) |cur_ver| {
        if (version <= cur_ver) {
            log.warn("rejected rollback for {s}: version {d} <= current {d}", .{ identity, version, cur_ver });
            return response.badRequest(writer, "version must be greater than current stored version");
        }
    }

    // Derive integrity key from guardian server_secret (or use fallback)
    const integrity_key: []const u8 = if (ctx.guardian) |guardian|
        &guardian.server_secret
    else
        "vault-default-integrity-key!!!!!";

    // Write share data to storage with HMAC integrity
    file_store.writeShare(ctx.data_dir, identity, share_data, integrity_key, ctx.allocator) catch |err| {
        log.err("failed to write share for {s}: {}", .{ identity, err });
        return response.internalError(writer);
    };

    // Write version file
    writeVersionFile(ctx.data_dir, identity, version) catch |err| {
        log.err("failed to write version for {s}: {}", .{ identity, err });
        // Share was written but version wasn't — not fatal, but log it
    };

    log.info("stored share for identity {s} ({d} bytes, version {d})", .{ identity, share_data.len, version });
    try response.jsonOk(writer, "{\"status\":\"stored\"}");
}

/// Read the current stored version for an identity. Returns null if no version file exists.
fn readCurrentVersion(data_dir: []const u8, identity: []const u8, allocator: std.mem.Allocator) ?u64 {
    var path_buf: [4096]u8 = undefined;
    const version_path = std.fmt.bufPrint(&path_buf, "{s}/shares/{s}/version", .{ data_dir, identity }) catch return null;

    const version_data = std.fs.cwd().readFileAlloc(allocator, version_path, 32) catch return null;
    defer allocator.free(version_data);

    return std.fmt.parseInt(u64, version_data, 10) catch null;
}

/// Write version counter atomically to: <data_dir>/shares/<identity>/version
fn writeVersionFile(data_dir: []const u8, identity: []const u8, version: u64) !void {
    var path_buf: [4096]u8 = undefined;
    const tmp_path = std.fmt.bufPrint(&path_buf, "{s}/shares/{s}/version.tmp", .{ data_dir, identity }) catch return error.PathTooLong;

    var path_buf2: [4096]u8 = undefined;
    const final_path = std.fmt.bufPrint(&path_buf2, "{s}/shares/{s}/version", .{ data_dir, identity }) catch return error.PathTooLong;

    var ver_buf: [20]u8 = undefined; // max u64 is 20 digits
    const ver_str = std.fmt.bufPrint(&ver_buf, "{d}", .{version}) catch return error.PathTooLong;

    const file = try std.fs.cwd().createFile(tmp_path, .{});
    defer file.close();
    try file.writeAll(ver_str);

    std.fs.cwd().rename(tmp_path, final_path) catch |rename_err| {
        std.fs.cwd().deleteFile(tmp_path) catch {};
        return rename_err;
    };
}
