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

const LoginReq = struct {
    username: ?[]const u8,
    email: ?[]const u8,
    password: []const u8,
};

// POST endpoint for user login requests
pub fn login(req: *httpz.Request, res: *httpz.Response) !void {
    const db = handler.getDb();

    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();

    const body_data = req.json(LoginReq) catch |err| {
        std.debug.print("Malformed body: {any}\n", .{err});
        handler.returnError("Bad Request: Malformed Body", 400, res);
        return;
    };

    if (body_data == null) {
        handler.returnError("Bad Request: No Data", 400, res);
        return;
    }
    const formatted_now = ztime.DateTime.now().formatAlloc(allocator, "DD.MM.YYYY HH:mm:ss") catch "N/A";
    std.log.info("{s} {s} @ {s}\n", .{ @tagName(req.method), req.url.raw, formatted_now });

    var body = body_data.?;
    if (body.username == null and body.email == null) {
        handler.returnError("Bad Request: Missing username / email", 400, res);
        return;
    } else if (body.username != null and body.email != null) {
        handler.returnError("Bad Request: Provided Username and Email", 400, res);
        return;
    }

    var user: ?models.User = null;
    const password_hash = try utils.hashPassword(allocator, body.password);
    if (body.username != null) {
        const query =
            "WHERE pass_hash = ? and username = ?;";
        user = try db.selectOne(models.User, allocator, try models.createRawSelectQuery(
            models.User,
            query,
        ), .{ .pass_hash = password_hash, .username = body.username.? });
    } else {
        const query =
            "WHERE pass_hash = ? and email = ?;";
        user = try db.selectOne(models.User, allocator, try models.createRawSelectQuery(
            models.User,
            query,
        ), .{ .pass_hash = password_hash, .email = body.email.? });
    }

    if (user == null) {
        handler.returnError("User not found or incorrect password", 200, res);
        return;
    } else if (user.?.hide == 1) {
        handler.returnError("Account has been closed", 200, res);
        return;
    }
    const token = try auth.generateToken(user.?);

    try handler.returnData(.{ .token = token }, res);
}

const SignupReq = struct {
    name: []const u8,
    username: []const u8,
    email: ?[]const u8,
    password: []const u8,
    family_code: ?[]const u8,
    budget_name: []const u8,
};

// POST endpoint for user signups
pub fn signup(req: *httpz.Request, res: *httpz.Response) !void {
    const db = handler.getDb();

    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();

    const body_data = req.json(SignupReq) catch |err| {
        std.debug.print("Malformed body: {any}\n", .{err});
        handler.returnError("Bad Request: Malformed Body", 400, res);
        return;
    };

    if (body_data == null) {
        handler.returnError("Bad Request: No Data", 400, res);
        return;
    }

    const formatted_now = ztime.DateTime.now().formatAlloc(allocator, "DD.MM.YYYY HH:mm:ss") catch "N/A";
    std.log.info("{s} {s} @ {s}\n", .{ @tagName(req.method), req.url.raw, formatted_now });

    var body = body_data.?;
    // if (body.username == null and body.email == null) {
    //     handler.returnError("Bad Request: Missing username / email", 400, res);
    //     return;
    // }

    const password_hash = try utils.hashPassword(allocator, body.password);
    const now = @bitCast(u64, std.time.milliTimestamp());

    const uname_query =
        "WHERE username = ?;";
    var temp_user = try db.selectOne(models.User, allocator, try models.createRawSelectQuery(
        models.User,
        uname_query,
    ), .{ .username = body.username });

    if (temp_user != null) {
        handler.returnError("Username is unavailable", 200, res);
        return;
    }

    if (body.email != null) {
        const email_query =
            "WHERE email = ?;";
        temp_user = try db.selectOne(models.User, allocator, try models.createRawSelectQuery(
            models.User,
            email_query,
        ), .{ .email = body.email.? });

        if (temp_user != null) {
            handler.returnError("Email is unavailable", 200, res);
            return;
        }
    }

    var family: ?models.Family = null;
    var budget: ?models.Budget = null;
    var user: ?models.User = null;
    if (body.family_code == null) {
        const new_family = .{
            .code = utils.generateRandomString(allocator) catch null,
            .created_at = now,
            .updated_at = now,
            .hide = 0,
        };
        try db.insert(models.Family, new_family);
        const family_query = try models.createSelectOnFieldQuery(models.Family, null, "code", "=");
        family = try db.selectOne(models.Family, allocator, family_query, .{ .code = new_family.code });

        if (family == null) {
            handler.returnError("Unable to create family", 500, res);
            return;
        }

        const new_budget = .{
            .family_id = family.?.id,
            .name = body.budget_name,
            .created_at = now,
            .updated_at = now,
            .hide = 0,
            .expected_income = null,
        };
        try db.insert(models.Budget, new_budget);
        const budget_query = try models.createSelectOnFieldQuery(models.Budget, null, "created_at", "=");
        budget = try db.selectOne(models.Budget, allocator, budget_query, .{ .created_at = now });

        if (budget == null) {
            handler.returnError("Unable to create budget", 500, res);
            return;
        }
    } else {
        const family_query = try models.createSelectOnFieldQuery(models.Family, null, "code", "=");
        family = try db.selectOne(models.Family, allocator, family_query, .{ .code = body.family_code.? });

        if (family == null) {
            return handler.returnError("Invalid Family Code", 404, res);
        }

        const budget_query = try models.createSelectOnFieldQuery(models.Budget, null, "family_id", "=");
        budget = try db.selectOne(models.Budget, allocator, budget_query, .{ .family_id = family.?.id });
        if (budget == null) {
            return handler.returnError("Could not find Family Budget", 404, res);
        }
    }

    const new_user = .{ .name = body.name, .username = body.username, .email = body.email, .pass_hash = password_hash, .family_id = family.?.id, .budget_id = budget.?.id, .created_at = now, .updated_at = now, .last_activity_at = now, .hide = 0 };

    try db.insert(models.User, new_user);

    const query = try models.createSelectOnFieldQuery(models.User, null, "pass_hash", "=");
    user = try db.selectOne(models.User, allocator, query, .{ .pass_hash = password_hash });

    if (user == null) {
        handler.returnError("Unable to create new user", 500, res);
        return;
    }

    // std.log.info("User created: {any}\nFamily created: {any}\n", .{ user.?, family.? });

    const token = try auth.generateToken(user.?);

    try handler.returnData(.{ .user = user, .token = token }, res);
}

pub fn getUser(req: *httpz.Request, res: *httpz.Response) !void {
    const db = handler.getDb();

    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    const id_str = req.param("id");
    if (id_str == null) {
        handler.returnError("Bad Request: No Id", 400, res);
        return;
    }
    const id = std.fmt.parseInt(u32, id_str.?, 0) catch {
        handler.returnError("Bad Request: Bad Id", 401, res);
        return;
    };

    const user = try db.selectOneById(models.User, allocator, id);

    if (user == null) {
        _ = auth.verifyRequest(req, res, id, user.family_id) catch {
            return;
        };
        handler.returnError("Error: User Not Found of Forbidden", 403, res);
        return;
    }

    try handler.returnData(user.?, res);
}

const UserPostReq = struct {
    name: []const u8,
    family_id: u32,
    budget_id: u32,
    created_at: ?u64,
    last_activity_at: ?u64,
    updated_at: ?u64,
    hide: u8,
};

pub fn putUser(req: *httpz.Request, res: *httpz.Response) !void {
    const db = handler.getDb();
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();

    const body_data = req.json(models.User) catch |err| {
        std.debug.print("Malformed body: {any}\n", .{err});
        handler.returnError("Bad request: Malformed Body", 400, res);
        return;
    };
    if (body_data == null) {
        handler.returnError("Bad request: No Data", 400, res);
        return;
    }
    var body = body_data.?;

    _ = auth.verifyRequest(req, res, body.id, body.family_id) catch {
        return;
    };

    // Add User
    const now = @intCast(u64, std.time.milliTimestamp());
    // Update existing User

    body.updated_at = now;
    body.last_activity_at = now;
    try db.updateById(models.User, body);

    const query = models.createSelectOnIdQuery(models.User);
    const updated_user = try db.selectOne(models.User, allocator, query, .{ .id = body.id });
    if (updated_user) |user| {
        try handler.returnData(user, res);
    } else {
        handler.returnError("Internal Server Error", 500, res);
    }
    return;
    // try res.json(user, .{});
}

// pub fn postUser(req: *httpz.Request, res: *httpz.Response) !void {
//     comptime {
//         const putReqLen = @typeInfo(UserPostReq).Struct.fields.len;
//         const userLen = @typeInfo(models.User).Struct.fields.len;
//         if (putReqLen != userLen) {
//             @compileError(std.fmt.comptimePrint("UserPutReq does not equal User model struct, fields inconsistent", .{}));
//         }
//     }

//     const db = handler.getDb();
//     var gpa = std.heap.GeneralPurposeAllocator(.{}){};
//     const allocator = gpa.allocator();

//     const body_data = req.json(UserPostReq) catch |err| {
//         std.debug.print("Malformed body: {any}\n", .{err});
//         handler.returnError("Bad request: Malformed Body", 400, res);
//         return;
//     };
//     if (body_data == null) {
//         handler.returnError("Bad request: No Data", 400, res);
//         return;
//     }
//     var body = body_data.?;

//     if (body.id != null) {
//         handler.returnError("Bad request: ID", 400, res);
//         return;
//     }
//     // Add User
//     const now = @intCast(u64, std.time.milliTimestamp());
//     // Create User
//     body.created_at = now;
//     body.last_activity_at = now;
//     body.updated_at = now;

//     try db.insert(models.User, utils.removeStructFields(body, &[_]u8{0}));

//     // Get new User
//     const query = try models.createSelectOnFieldQuery(models.User, null, "created_at", "=");
//     const updated_user = try db.selectOne(models.User, allocator, query, .{ .created_at = body.created_at });
//     if (updated_user) |user| {
//         try handler.returnData(user, res);
//     } else {
//         handler.returnError("Internal Server Error", 500, res);
//     }
//     return;
// }

// pub fn deleteUser(req: *httpz.Request, res: *httpz.Response) !void {
//     const db = handler.getDb();

//     const user_id = req.param("id");
//     if (res.body) |_| {
//         handler.returnError("Bad Request", 400, res);
//         return;
//     }
//     if (user_id) |id_str| {
//         const id = std.fmt.parseInt(u32, id_str, 0) catch {
//             handler.returnError("Bad Request: Invalid Id", 400, res);
//             return;
//         };
//         db.deleteById(models.User, id) catch |err| {
//             std.debug.print("Error while deleting user: {}\n", .{err});
//             handler.returnError("Internal Server Error", 500, res);
//             return;
//         };
//     } else {
//         handler.returnError("Bad Request: Missing ID", 400, res);
//     }
//     return;
// }
