const std = @import("std");
const vault_mod = @import("vault.zig");

const Vault = vault_mod.Vault;

const RunError = error{
    MissingMasterPassword,
    UnknownCommand,
    WriteFailed,
} || std.mem.Allocator.Error || vault_mod.CreateError || vault_mod.OpenError;

const Command = enum {
    init,
    open,
    unknown,
    fn parse(raw: []const u8) Command {
        if (std.mem.eql(u8, raw, "init")) return .init;
        if (std.mem.eql(u8, raw, "open")) return .open;
        return .unknown;
    }
};

fn usage(out: *std.Io.Writer) !void {
    try out.writeAll("TODO: ask claude to generate this");
}

fn hasFlag(args: []const [:0]const u8, flag: []const u8) bool {
    for (args[1..]) |arg| {
        if (std.mem.eql(u8, arg, flag)) return true;
    }
    return false;
}

fn valueOfFrom(args: []const [:0]const u8, start_index: usize, flag: []const u8) ?[]const u8 {
    var i = start_index;
    while (i < args.len) : (i += 1) {
        if (std.mem.eql(u8, args[i], flag)) {
            if (i + 1 == args.len) return null;
            return args[i + 1];
        }
    }
    return null;
}

fn passwordOption(args: []const [:0]const u8, start_index: usize) ?[]const u8 {
    return valueOfFrom(args, start_index, "-p") orelse valueOfFrom(args, start_index, "--password");
}

fn run(allocator: std.mem.Allocator, io: std.Io, out: *std.Io.Writer, args: []const [:0]const u8) RunError!void {
    if (args.len < 2 or hasFlag(args, "--help")) {
        try usage(out);
        return;
    }

    const command = Command.parse(args[1]);

    switch (command) {
        .init => {
            const raw_password = passwordOption(args, 2) orelse return error.MissingMasterPassword;
            var vault = Vault.init(allocator, io, raw_password) catch |err| switch (err) {
                error.VaultAlreadyExists => {
                    try out.print("`{s}` has already been initialized.\n", .{"pak.db"});
                    return;
                },
                else => return err,
            };
            defer vault.close();
            // TODO: make path configurable
            try out.print("initialized vault at `{s}`\n", .{"pak.db"});
        },
        .open => {
            const raw_password = passwordOption(args, 2) orelse return error.MissingMasterPassword;
            var vault = Vault.open(allocator, io, raw_password) catch |err| switch (err) {
                error.InvalidMasterPassword => {
                    try out.writeAll("verifier: failed\n");
                    return;
                },
                else => return err,
            };
            defer vault.close();
            try out.writeAll("verifier: ok\nopened\n");
        },
        .unknown => return error.UnknownCommand,
    }
}

pub fn main(init: std.process.Init) !void {
    const allocator = init.arena.allocator();
    const args = try init.minimal.args.toSlice(allocator);

    var stdout_buffer: [4096]u8 = undefined;
    var stdout_writer = std.Io.File.stdout().writer(init.io, &stdout_buffer);
    const out = &stdout_writer.interface;
    defer out.flush() catch {};

    run(allocator, init.io, out, args) catch |err| switch (err) {
        error.MissingMasterPassword => try out.writeAll("error: missing master password\n"),
        error.UnknownCommand => try out.writeAll("error: unknown command\n"),
        else => return err,
    };
}

test "init password accepts short and long password flags" {
    const short_args = [_][:0]const u8{ "pak", "init", "-p", "secret" };
    try std.testing.expectEqualStrings("secret", passwordOption(&short_args, 2).?);

    const long_args = [_][:0]const u8{ "pak", "init", "--password", "secret" };
    try std.testing.expectEqualStrings("secret", passwordOption(&long_args, 2).?);
}

test "command parser recognizes open command" {
    try std.testing.expectEqual(Command.init, Command.parse("init").?);
    try std.testing.expectEqual(Command.open, Command.parse("open").?);
    try std.testing.expectEqual(@as(?Command, null), Command.parse("unknown"));
}
