const std = @import("std");

const INPUTS_DIR = "inputs";
const SESSION_FILE = "session.txt";
const MAX_INPUT_SIZE = 512 * 512; // Maximum allowed input size (bytes)

pub const Options = struct {
    year: usize,
    days: usize,
};

pub fn fetchInputs(allocator: std.mem.Allocator, opts: Options) !void {
    const cwd = std.fs.cwd();

    cwd.makeDir(INPUTS_DIR) catch |err| {
        switch (err) {
            error.PathAlreadyExists => {},
            else => return err,
        }
    };

    const session = cwd.readFileAlloc(allocator, SESSION_FILE, 256) catch |err| {
        std.log.err("Unable to open '{s}'.\nMake sure to copy the session cookie value to this file to download the user specific input files.", .{SESSION_FILE});
        return err;
    };
    defer allocator.free(session);

    for (1..opts.days + 1) |day| {
        const file_name = try std.fmt.allocPrint(allocator, "{s}/day{d:0>2}.txt", .{ INPUTS_DIR, day });
        defer allocator.free(file_name);

        const fd = cwd.openFile(file_name, std.fs.File.OpenFlags{}) catch |err| {
            switch (err) {
                error.FileNotFound => {
                    try fetchDay(allocator, opts, session, day);
                    continue;
                },
                else => return err,
            }
        };
        defer fd.close();
    }
}

fn fetchDay(allocator: std.mem.Allocator, opts: Options, session: []const u8, day: usize) !void {
    var client = std.http.Client{ .allocator = allocator };
    defer client.deinit();

    std.debug.print("Downloading day{d:0>2}\r", .{day});

    const url = try std.fmt.allocPrint(
        allocator,
        "https://adventofcode.com/{d}/day/{d}/input",
        .{ opts.year, day },
    );
    defer allocator.free(url);

    const uri = try std.Uri.parse(url);

    var headers = std.ArrayList(std.http.Header).init(allocator);
    defer headers.deinit();

    try headers.append(.{
        .name = "Cookie",
        .value = try std.fmt.allocPrint(allocator, "session={s}", .{session}),
    });
    defer {
        for (headers.items) |header| {
            allocator.free(header.value);
        }
    }

    var sh: [512]u8 = undefined;
    var req = try client.open(.GET, uri, .{
        .server_header_buffer = &sh,
        .extra_headers = headers.items,
    });
    defer req.deinit();

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

    const body = try req.reader().readAllAlloc(allocator, MAX_INPUT_SIZE);
    defer allocator.free(body);

    if (req.response.status != std.http.Status.ok) {
        std.log.err("{}", .{req.response.status});
        std.log.err("{s}", .{body});
        return error.HttpRequestError;
    }

    const file_name = try std.fmt.allocPrint(allocator, "{s}/day{d:0>2}.txt", .{ INPUTS_DIR, day });
    defer allocator.free(file_name);

    try std.fs.cwd().writeFile(std.fs.Dir.WriteFileOptions{
        .sub_path = file_name,
        .data = body,
        .flags = .{ .mode = 0o644 },
    });
}

/// Checks if `INPUTS_DIR` exists and all the input files are available.
pub fn checkInputs(days: usize) bool {
    const cwd = std.fs.cwd();
    const inputs = cwd.openDir(INPUTS_DIR, std.fs.Dir.OpenDirOptions{
        .iterate = true,
    }) catch {
        return false;
    };

    var counter: usize = 0;
    var it = inputs.iterate();
    while (it.next() catch unreachable) |_| : (counter += 1) {}

    if (counter == days) return true else return false;
}