/// File-per-user share storage with HMAC integrity.
///
/// Each user's data is stored in a directory named by their identity hash (hex).
/// Files are written atomically (write-to-temp + rename) to prevent corruption.
///
/// Directory layout:
///   <data_dir>/shares/<identity_hash_hex>/
///     meta.json          - Share metadata (JSON)
///     share.bin          - Raw encrypted share data
///     wrapped_dek1.bin   - KEK1-wrapped DEK
///     wrapped_dek2.bin   - KEK2-wrapped DEK
///     checksum.bin       - HMAC-SHA256 of share.bin
const std = @import("std");
const hmac = @import("../crypto/hmac.zig");

pub const StoreError = error{
    IdentityHashRequired,
    ShareDataRequired,
    IntegrityCheckFailed,
    OutOfMemory,
    IoError,
};

/// Metadata for a stored share.
pub const ShareMetadata = struct {
    /// Monotonic version counter (for rollback protection)
    version: u64,
    /// Share index (x-coordinate in Shamir scheme)
    share_index: u8,
    /// Merkle commitment root (hex)
    commitment_root: [64]u8, // 32 bytes as hex
    /// Timestamp of last update (Unix epoch)
    timestamp: i64,
};

/// Writes share data atomically to the store.
/// Creates directory if it doesn't exist. Uses temp+rename for atomicity.
pub fn writeShare(
    data_dir: []const u8,
    identity_hash_hex: []const u8,
    share_data: []const u8,
    integrity_key: []const u8,
    allocator: std.mem.Allocator,
) !void {
    if (identity_hash_hex.len == 0) return StoreError.IdentityHashRequired;
    if (share_data.len == 0) return StoreError.ShareDataRequired;

    // Build path: <data_dir>/shares/<identity_hash_hex>/
    const share_dir = try std.fmt.allocPrint(allocator, "{s}/shares/{s}", .{ data_dir, identity_hash_hex });
    defer allocator.free(share_dir);

    // Create directory
    std.fs.cwd().makePath(share_dir) catch {
        return StoreError.IoError;
    };

    // Write share data atomically
    const share_path = try std.fmt.allocPrint(allocator, "{s}/share.bin", .{share_dir});
    defer allocator.free(share_path);
    const tmp_path = try std.fmt.allocPrint(allocator, "{s}/share.bin.tmp", .{share_dir});
    defer allocator.free(tmp_path);

    try atomicWrite(share_path, tmp_path, share_data);

    // Write HMAC checksum
    const checksum = hmac.compute(integrity_key, share_data);
    const checksum_path = try std.fmt.allocPrint(allocator, "{s}/checksum.bin", .{share_dir});
    defer allocator.free(checksum_path);
    const checksum_tmp = try std.fmt.allocPrint(allocator, "{s}/checksum.bin.tmp", .{share_dir});
    defer allocator.free(checksum_tmp);

    try atomicWrite(checksum_path, checksum_tmp, &checksum);
}

/// Reads share data from the store and verifies HMAC integrity.
/// Returns the share data. Caller must free.
pub fn readShare(
    data_dir: []const u8,
    identity_hash_hex: []const u8,
    integrity_key: []const u8,
    allocator: std.mem.Allocator,
) ![]u8 {
    // Build paths
    const share_path = try std.fmt.allocPrint(allocator, "{s}/shares/{s}/share.bin", .{ data_dir, identity_hash_hex });
    defer allocator.free(share_path);
    const checksum_path = try std.fmt.allocPrint(allocator, "{s}/shares/{s}/checksum.bin", .{ data_dir, identity_hash_hex });
    defer allocator.free(checksum_path);

    // Read share data
    const share_data = std.fs.cwd().readFileAlloc(allocator, share_path, 10 * 1024 * 1024) catch {
        return StoreError.IoError;
    };
    errdefer allocator.free(share_data);

    // Read checksum
    const checksum_data = std.fs.cwd().readFileAlloc(allocator, checksum_path, 32) catch {
        return StoreError.IoError;
    };
    defer allocator.free(checksum_data);

    if (checksum_data.len != 32) {
        return StoreError.IntegrityCheckFailed;
    }

    // Verify HMAC
    if (!hmac.verify(integrity_key, share_data, checksum_data[0..32].*)) {
        return StoreError.IntegrityCheckFailed;
    }

    return share_data;
}

/// Checks if a share exists for the given identity.
pub fn shareExists(
    data_dir: []const u8,
    identity_hash_hex: []const u8,
    allocator: std.mem.Allocator,
) !bool {
    const share_path = try std.fmt.allocPrint(allocator, "{s}/shares/{s}/share.bin", .{ data_dir, identity_hash_hex });
    defer allocator.free(share_path);

    std.fs.cwd().access(share_path, .{}) catch {
        return false;
    };
    return true;
}

/// Deletes all data for the given identity.
pub fn deleteShare(
    data_dir: []const u8,
    identity_hash_hex: []const u8,
    allocator: std.mem.Allocator,
) !void {
    const share_dir = try std.fmt.allocPrint(allocator, "{s}/shares/{s}", .{ data_dir, identity_hash_hex });
    defer allocator.free(share_dir);

    std.fs.cwd().deleteTree(share_dir) catch {};
}

// ── Internal helpers ─────────────────────────────────────────────────────────

fn atomicWrite(final_path: []const u8, tmp_path: []const u8, data: []const u8) !void {
    // Write to temp file
    const tmp_file = std.fs.cwd().createFile(tmp_path, .{}) catch {
        return StoreError.IoError;
    };
    defer tmp_file.close();
    tmp_file.writeAll(data) catch {
        return StoreError.IoError;
    };

    // Rename atomically
    std.fs.cwd().rename(tmp_path, final_path) catch {
        return StoreError.IoError;
    };
}

// ── Tests ────────────────────────────────────────────────────────────────────

test "write and read share round-trip" {
    const allocator = std.testing.allocator;

    // Use a temp directory
    var tmp_dir_buf: [std.fs.max_path_bytes]u8 = undefined;
    var tmp_dir = std.testing.tmpDir(.{});
    defer tmp_dir.cleanup();
    const tmp_path = try tmp_dir.dir.realpath(".", &tmp_dir_buf);

    const identity = "abcdef0123456789";
    const share_data = "test share data bytes";
    const key = "integrity-key-32-bytes-long!!!!";

    try writeShare(tmp_path, identity, share_data, key, allocator);

    const read_data = try readShare(tmp_path, identity, key, allocator);
    defer allocator.free(read_data);

    try std.testing.expectEqualSlices(u8, share_data, read_data);
}

test "integrity check detects tampering" {
    const allocator = std.testing.allocator;

    var tmp_dir_buf: [std.fs.max_path_bytes]u8 = undefined;
    var tmp_dir = std.testing.tmpDir(.{});
    defer tmp_dir.cleanup();
    const tmp_path = try tmp_dir.dir.realpath(".", &tmp_dir_buf);

    const identity = "deadbeef";
    const share_data = "original data";
    const key = "integrity-key-32-bytes-long!!!!";

    try writeShare(tmp_path, identity, share_data, key, allocator);

    // Tamper with the share file directly
    const share_path = try std.fmt.allocPrint(allocator, "{s}/shares/{s}/share.bin", .{ tmp_path, identity });
    defer allocator.free(share_path);

    const file = try std.fs.cwd().openFile(share_path, .{ .mode = .write_only });
    defer file.close();
    try file.writeAll("tampered data!");

    // Read should fail integrity check
    try std.testing.expectError(StoreError.IntegrityCheckFailed, readShare(tmp_path, identity, key, allocator));
}

test "shareExists: returns false for missing share" {
    const allocator = std.testing.allocator;

    var tmp_dir_buf: [std.fs.max_path_bytes]u8 = undefined;
    var tmp_dir = std.testing.tmpDir(.{});
    defer tmp_dir.cleanup();
    const tmp_path = try tmp_dir.dir.realpath(".", &tmp_dir_buf);

    const exists = try shareExists(tmp_path, "nonexistent", allocator);
    try std.testing.expect(!exists);
}

test "shareExists: returns true for existing share" {
    const allocator = std.testing.allocator;

    var tmp_dir_buf: [std.fs.max_path_bytes]u8 = undefined;
    var tmp_dir = std.testing.tmpDir(.{});
    defer tmp_dir.cleanup();
    const tmp_path = try tmp_dir.dir.realpath(".", &tmp_dir_buf);

    try writeShare(tmp_path, "exists", "data", "key-32-bytes-exactly-right!!!!!", allocator);

    const exists = try shareExists(tmp_path, "exists", allocator);
    try std.testing.expect(exists);
}

test "deleteShare: removes all files" {
    const allocator = std.testing.allocator;

    var tmp_dir_buf: [std.fs.max_path_bytes]u8 = undefined;
    var tmp_dir = std.testing.tmpDir(.{});
    defer tmp_dir.cleanup();
    const tmp_path = try tmp_dir.dir.realpath(".", &tmp_dir_buf);

    try writeShare(tmp_path, "todelete", "data", "key-32-bytes-exactly-right!!!!!", allocator);
    try deleteShare(tmp_path, "todelete", allocator);

    const exists = try shareExists(tmp_path, "todelete", allocator);
    try std.testing.expect(!exists);
}
