const std = @import("std");
const crypto = @import("crypto.zig");

pub const ContentAddressedStorage = struct {
    allocator: std.mem.Allocator,
    base_path: []const u8,

    pub fn init(allocator: std.mem.Allocator, base_path: []const u8) ContentAddressedStorage {
        return .{
            .allocator = allocator,
            .base_path = base_path,
        };
    }

    pub fn storeFile(self: *ContentAddressedStorage, content: []const u8) ![]const u8 {
        // Hash content to get address
        const hash = try crypto.hashString(self.allocator, content);
        defer self.allocator.free(hash);

        // Create content path
        const content_path = try std.fmt.allocPrint(self.allocator, "{s}/content/{s}/{s}", .{ self.base_path, hash[0..2], hash });
        defer self.allocator.free(content_path);

        // Check if content already exists
        {
            const file = std.fs.openFileAbsolute(content_path, .{}) catch |err| {
                if (err == error.FileNotFound) {
                    // Store new content - simplified approach
                    const parent_dir = std.fs.path.dirname(content_path) orelse return error.InvalidPath;
                    std.fs.cwd().makePath(parent_dir) catch |make_err| {
                        if (make_err != error.PathAlreadyExists) return make_err;
                    };

                    const file = try std.fs.createFileAbsolute(content_path, .{});
                    defer file.close();

                    try file.writeAll(content);
                    return self.allocator.dupe(u8, hash);
                }
                return err;
            };
            defer file.close();

            // Content already exists
            return self.allocator.dupe(u8, hash);
        }
    }

    pub fn linkFile(self: *ContentAddressedStorage, content_hash: []const u8, target_path: []const u8) !void {
        const content_path = try std.fmt.allocPrint(self.allocator, "{s}/content/{s}/{s}", .{ self.base_path, content_hash[0..2], content_hash });
        defer self.allocator.free(content_path);

        // Create parent directory if needed
        const parent_dir = std.fs.path.dirname(target_path) orelse return error.InvalidPath;
        std.fs.cwd().makePath(parent_dir) catch |make_err| {
            if (make_err != error.PathAlreadyExists) return make_err;
        };

        // Copy file instead of hard link to avoid permission issues
        const src_file = try std.fs.openFileAbsolute(content_path, .{});
        defer src_file.close();

        const dest_file = try std.fs.createFileAbsolute(target_path, .{});
        defer dest_file.close();

        const content = try src_file.readToEndAlloc(self.allocator, 1024 * 1024 * 100);
        defer self.allocator.free(content);

        try dest_file.writeAll(content);
    }

    pub fn deduplicateDirectory(self: *ContentAddressedStorage, dir_path: []const u8) !void {
        var dir = try std.fs.openDirAbsolute(dir_path, .{ .iterate = true });
        defer dir.close();

        var walker = try dir.walk(self.allocator);
        defer walker.deinit();

        while (try walker.next()) |entry| {
            if (entry.kind == .file) {
                const file = try dir.openFile(entry.path, .{});
                defer file.close();

                const content = try file.readToEndAlloc(self.allocator, 1024 * 1024 * 100); // 100MB limit
                defer self.allocator.free(content);

                const hash = try self.storeFile(content);
                defer self.allocator.free(hash);

                const target_path = try std.fmt.allocPrint(self.allocator, "{s}/{s}", .{ dir_path, entry.path });
                defer self.allocator.free(target_path);

                try self.linkFile(hash, target_path);
            }
        }
    }
};
