const std = @import("std");
const jwt = @import("../.deps/jwt.zig");
const httpz = @import("../.deps/http.zig/src/httpz.zig");
const models = @import("../db/models.zig");
const ztime = @import("../.deps/time.zig");
const handler = @import("../http_handler.zig");
const utils = @import("../utils.zig");

//TODO move these to env variables

const key = "aGVyZUlzQUdpYmVyaXNoS2V5ISE=";

pub const VerifyAuthError = error{
    Unauthorized,
    NotAuthenticated,
    BadToken,
    Expired,
};

pub fn verifyRequest(req: *httpz.Request, res: *httpz.Response, user_id: ?u32, family_id: ?u32) !models.Token {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    const coded_token = req.headers.get("token");
    const now =
        @bitCast(u64, std.time.milliTimestamp());
    const date = ztime.DateTime.now();
    const formatted_now = date.formatAlloc(allocator, "DD.MM.YYYY HH:mm:ss") catch "N/A";

    const method = @tagName(req.method);

    if (coded_token == null) {
        handler.returnError("Unauthorized/NoToken", 401, res);
        std.log.info("{s} {s} Unauthorized/NotAuthenticated - @ {s}", .{ method, req.url.query, formatted_now });
        return VerifyAuthError.NotAuthenticated;
    }

    const token = jwt.validate(models.Token, allocator, .HS256, coded_token.?, .{ .key = key }) catch {
        handler.returnError("Unauthorized", 400, res);
        std.log.info("{s} {s} Unauthorized/BadToken - Token: {s} @ {s}", .{ method, req.url.raw, coded_token.?, formatted_now });
        return VerifyAuthError.BadToken;
    };

    if (user_id != null and user_id.? != token.user_id or family_id != null and family_id.? != token.family_id) {
        handler.returnError("Unauthorized", 401, res);
        std.log.info("{s} {s} Unauthorized/BadCredentials - User: {} Family: {any} @ {s}, Tried User: {any} Family: {any}", .{ method, req.url.raw, token.user_id, token.family_id, formatted_now, user_id, family_id });
        return VerifyAuthError.Unauthorized;
    }

    if (token.expires_at < now) {
        std.log.info("{s} {s} Unauthorized/Expired - User: {} Family: {any} @ {s}", .{ method, req.url.raw, token.user_id, token.family_id, formatted_now });
        handler.returnError("Credentials Expired", 403, res);
        return VerifyAuthError.Expired;
    }

    std.log.info("{s} {s} Authorized - User: {} Family: {any} @ {s}", .{ method, req.url.raw, token.user_id, token.family_id, formatted_now });
    return token;
}

pub fn generateToken(user: models.User) ![]const u8 {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    const now = @bitCast(u64, std.time.milliTimestamp());
    var seven_days = ztime.DateTime.now();
    seven_days = seven_days.addDays(7);
    const token: models.Token = .{ .user_id = user.id, .family_id = user.family_id, .generated_at = now, .expires_at = seven_days.toUnixMilli() };
    const encoded_token = try jwt.encode(allocator, .HS256, token, .{ .key = key });
    // std.log.info("Generated token for User {} @ {}", .{ user.id, now });
    return encoded_token;
}

test {
    var gpa = std.testing.allocator_instance;
    const allocator = gpa.allocator();

    const user = models.User{ .id = 1, .family_id = 1, .budget_id = 1, .name = "Billy", .created_at = 1, .updated_at = 1, .last_activity_at = 1, .hide = 0 };
    const user_token = try generateToken(user);

    // std.debug.print("user_token: {s}\n", .{user_token});
    // var key = try std.testing.allocator.alloc(u8, try base64url.Decoder.calcSizeForSlice(key_base64));
    // defer std.testing.allocator.free(key);
    // try base64url.Decoder.decode(key, key_base64);

    // std.debug.print("key: {s}\n", .{key});

    // const user_token: models.Token = .{ .user_id = 1, .family_id = 1, .generated_at = @bitCast(u64, std.time.milliTimestamp()), .expires_at = @bitCast(u64, std.time.milliTimestamp()) };
    // const coded_message = try jwt.encode(allocator, .HS256, user_token, .{ .key = key });
    const decoded_message = try jwt.validate(models.Token, allocator, .HS256, user_token, .{ .key = key });
    std.debug.print("user: {any}\nuser_token: {s}\ndecoded: {any}\n", .{ user, user_token, decoded_message });
}
