const std = @import("std");
const wardrobe = @import("wardrobe");

const server = @import("eink_feed_server");
const protocol = @import("eink_feed_protocol");

pub const Error = error{
    InvalidResponse,
};

const Self = @This();

allocator: std.mem.Allocator,
channel_url_str: []const u8,
frame_url_str: []const u8,

pub fn init(allocator: std.mem.Allocator, base_url: []const u8) !Self {
    return .{
        .allocator = allocator,
        .channel_url_str = try std.fmt.allocPrint(allocator, "{s}/api/channel", .{base_url}),
        .frame_url_str = try std.fmt.allocPrint(allocator, "{s}/api/frame", .{base_url}),
    };
}

pub fn deinit(self: *const Self) void {
    self.allocator.free(self.channel_url_str);
    self.allocator.free(self.frame_url_str);
}

pub fn getChannel(self: *const Self, channel_id: server.models.Channel.Id) !std.json.Parsed(server.web.api.models.Channel) {
    var client = std.http.Client{ .allocator = self.allocator };
    defer client.deinit();

    const url_str = try std.fmt.allocPrint(
        self.allocator,
        "{s}?id={d}",
        .{ self.channel_url_str, channel_id },
    );
    defer self.allocator.free(url_str);
    const url = try std.Uri.parse(url_str);

    var buf: [4096]u8 = undefined;
    var req = try client.open(.GET, url, .{
        .server_header_buffer = &buf,
    });
    defer req.deinit();

    try req.send();
    try req.finish();
    try req.wait();

    if (req.response.status != .ok)
        return Error.InvalidResponse;

    const body = try req.reader().readAllAlloc(self.allocator, 1 * 1024 * 1024);
    defer self.allocator.free(body);

    const channel = try std.json.parseFromSlice(
        server.web.api.models.Channel,
        self.allocator,
        body,
        .{ .allocate = .alloc_always, .ignore_unknown_fields = true },
    );

    return channel;
}

pub fn uploadFrame(self: *const Self, channel_id: u32, orientation: protocol.models.Orientation, data: []const u8, content_type: []const u8) !void {
    var client = std.http.Client{ .allocator = self.allocator };
    defer client.deinit();

    const url_str = try std.fmt.allocPrint(
        self.allocator,
        "{s}?id={d}&orientation={s}",
        .{ self.frame_url_str, channel_id, orientation.toString() },
    );
    defer self.allocator.free(url_str);
    const url = try std.Uri.parse(url_str);

    var body = std.ArrayList(u8).init(self.allocator);
    defer body.deinit();

    var prng = std.Random.DefaultPrng.init(@intCast(std.time.microTimestamp()));
    const boundary: wardrobe.Boundary = .entropy("EinkFeedRenderBoundary", prng.random());
    var write_stream = wardrobe.writeStream(boundary, body.writer());

    try write_stream.beginFileEntry("img", content_type, "image.png");
    try write_stream.writer().writeAll(data);
    try write_stream.endEntry();

    try write_stream.endEntries();

    var buf: [4096]u8 = undefined;
    var req = try client.open(.POST, url, .{
        .server_header_buffer = &buf,
        .headers = .{
            .content_type = .{ .override = boundary.contentType() },
        },
    });
    defer req.deinit();
    req.transfer_encoding = .{ .content_length = body.items.len };

    try req.send();
    try req.writeAll(body.items);
    try req.finish();
    try req.wait();

    if (req.response.status != .created)
        return Error.InvalidResponse;
}
