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

/// Read entire file into allocated memory
pub fn readFileAlloc(allocator: std.mem.Allocator, path: []const u8) ![]u8 {
    var file = if (std.fs.path.isAbsolute(path))
        try std.fs.openFileAbsolute(path, .{})
    else
        try std.fs.cwd().openFile(path, .{});
    defer file.close();

    return file.readToEndAlloc(allocator, 1024 * 1024);
}

/// Resolve manifest path from input (path, run_id, or task_id)
pub fn resolvePathWithBase(
    allocator: std.mem.Allocator,
    input: []const u8,
    base: ?[]const u8,
) ![]u8 {
    var cwd = std.fs.cwd();

    if (std.fs.path.isAbsolute(input)) {
        if (std.fs.openDirAbsolute(input, .{}) catch null) |dir| {
            var mutable_dir = dir;
            defer mutable_dir.close();
            return std.fs.path.join(allocator, &[_][]const u8{ input, "run_manifest.json" });
        }
        if (std.fs.openFileAbsolute(input, .{}) catch null) |file| {
            var mutable_file = file;
            defer mutable_file.close();
            return allocator.dupe(u8, input);
        }
        if (base) |b| {
            return resolvePathById(allocator, input, b);
        }
        return error.FileNotFound;
    }

    const stat = cwd.statFile(input) catch |err| {
        if (err == error.FileNotFound) {
            if (base) |b| {
                return resolvePathById(allocator, input, b);
            }
        }
        return err;
    };

    if (stat.kind == .directory) {
        return std.fs.path.join(allocator, &[_][]const u8{ input, "run_manifest.json" });
    }

    return allocator.dupe(u8, input);
}

/// Resolve manifest path by searching for run_id or task_id in base directory
pub fn resolvePathById(
    allocator: std.mem.Allocator,
    id: []const u8,
    base_path: []const u8,
) ![]u8 {
    if (std.mem.trim(u8, id, " \t\r\n").len == 0) {
        return error.FileNotFound;
    }
    if (base_path.len == 0) {
        return error.FileNotFound;
    }

    const roots = [_][]const u8{ "finished", "failed", "running", "pending" };
    for (roots) |root| {
        const root_path = try std.fs.path.join(allocator, &[_][]const u8{ base_path, root });
        defer allocator.free(root_path);

        var dir = if (std.fs.path.isAbsolute(root_path))
            (std.fs.openDirAbsolute(root_path, .{ .iterate = true }) catch continue)
        else
            (std.fs.cwd().openDir(root_path, .{ .iterate = true }) catch continue);
        defer dir.close();

        var it = dir.iterate();
        while (try it.next()) |entry| {
            if (entry.kind != .directory) continue;

            const run_dir = try std.fs.path.join(allocator, &[_][]const u8{ root_path, entry.name });
            defer allocator.free(run_dir);
            const manifest_path = try std.fs.path.join(allocator, &[_][]const u8{ run_dir, "run_manifest.json" });
            defer allocator.free(manifest_path);

            const file = if (std.fs.path.isAbsolute(manifest_path))
                (std.fs.openFileAbsolute(manifest_path, .{}) catch continue)
            else
                (std.fs.cwd().openFile(manifest_path, .{}) catch continue);
            defer file.close();

            const data = file.readToEndAlloc(allocator, 1024 * 1024) catch continue;
            defer allocator.free(data);

            const parsed = std.json.parseFromSlice(std.json.Value, allocator, data, .{}) catch continue;
            defer parsed.deinit();
            if (parsed.value != .object) continue;

            const obj = parsed.value.object;
            const run_id = json.getString(obj, "run_id") orelse "";
            const task_id = json.getString(obj, "task_id") orelse "";
            if (std.mem.eql(u8, run_id, id) or std.mem.eql(u8, task_id, id)) {
                return allocator.dupe(u8, manifest_path);
            }
        }
    }

    return error.FileNotFound;
}

/// Read job_name from a manifest file
pub fn readJobNameFromManifest(allocator: std.mem.Allocator, manifest_path: []const u8) ![]u8 {
    const data = try readFileAlloc(allocator, manifest_path);
    defer allocator.free(data);

    const parsed = try std.json.parseFromSlice(std.json.Value, allocator, data, .{});
    defer parsed.deinit();

    if (parsed.value != .object) return error.InvalidManifest;
    const root = parsed.value.object;

    const job_name = json.getString(root, "job_name") orelse "";
    if (std.mem.trim(u8, job_name, " \t\r\n").len == 0) {
        return error.InvalidManifest;
    }
    return allocator.dupe(u8, job_name);
}
