const std = @import("std");
const httpz = @import("../.deps/http.zig/src/httpz.zig");
const models = @import("../db/models.zig");
const ztime = @import("../.deps/time.zig");
const utils = @import("../utils.zig");
const time = @import("../.deps/datetime.zig");
const tz = @import("../.deps/timezones.zig");

const auth = @import("auth.zig");
const handler = @import("../http_handler.zig");

pub fn fetchTransFromDb(allocator: std.mem.Allocator, family_id: u32) !?[]models.Transaction {
    var db = handler.getDb();
    const now = time.Datetime.now();
    var beginning_of_month = try time.Datetime.fromDate(now.date.year, now.date.month, 1);
    // const timezone_begin = beginning_of_month.shiftTimezone(&tz.US.Mountain);

    const timezone_begin = beginning_of_month.shiftHours(6);
    // std.log.info("Beginning: {} Shifted: {}", .{ beginning_of_month.date.toTimestamp(), timezone_begin.date.toTimestamp() });
    // const unix = @bitCast(u64, beginning_of_month.date.toTimestamp() + beginning_of_month.time.toTimestamp());
    const begin_time = @bitCast(u64, timezone_begin.date.toTimestamp() + timezone_begin.time.toTimestamp());

    // std.log.info("Fetching transactions after beginning of month: unix {}, adjusted {}", .{ unix, begin_time });
    comptime {
        if (!std.mem.eql(u8, @typeInfo(models.Transaction).Struct.fields[7].name, "date")) {
            return error{TransactionModelError};
        }
    }
    const budget_query = models.createSelectOnFieldQuery(models.Budget, null, "family_id", "=") catch |err| {
        std.debug.print("Error while creating budget query: {any}", .{err});
        return err;
    };
    const budget = db.selectOne(models.Budget, allocator, budget_query, .{ .family_id = family_id }) catch |err| {
        std.debug.print("Error while getting budget: {any}", .{err});
        return err;
    };
    if (budget == null) {
        return null;
    }
    const transactions = db.selectAllWhere(
        models.Transaction,
        allocator,
        "WHERE budget_id = ? AND date > ? AND hide = ?",
        .{ .budget_id = budget.?.id, .date = begin_time, .hide = 0 },
        null,
        null,
        null,
        null,
    ) catch |err| {
        std.debug.print("Error while getting transactions query: {any}", .{err});
        return err;
    };
    return transactions;
}

// pub fn getTransactions(req: *httpz.Request, res: *httpz.Response) !void {
//     auth.verifyRequest();
//     var gpa = std.heap.GeneralPurposeAllocator(.{}){};
//     const allocator = gpa.allocator();

//     const budget_id_str = req.param("budget_id");
//     if (budget_id_str == null) {
//         handler.returnError("Bad request", 400, res);
//         return;
//     }
//     const budget_id = std.fmt.parseInt(u32, budget_id_str.?, 0) catch {
//         handler.returnError("Bad request", 400, res);
//         return;
//     };

//     const transactions = try fetchTransFromDb(allocator, budget_id);

//     if (transactions == null) {
//         res.status = 200;
//         res.body = "";
//         return;
//     }
//     // std.debug.print("Transactions got:\n", .{});
//     // for (transactions.?) |transaction| {
//     //     std.debug.print("\t{any}\n", .{transaction});
//     // }
//     try handler.returnData(.{ .transactions = transactions.? }, res);
// }

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

    const body_data = req.json(models.Transaction) 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 transaction = body_data.?;

    const budget = try db.selectOneById(models.Budget, allocator, transaction.budget_id);
    if (budget == null) {
        return handler.returnError("Budget for transaction not found or forbidden", 403, res);
    }

    _ = auth.verifyRequest(req, res, transaction.created_by_user_id, budget.?.family_id) catch {
        return;
    };

    const now = @intCast(u64, std.time.milliTimestamp());
    transaction.updated_at = now;
    try db.updateById(models.Transaction, transaction);

    const query = models.createSelectOnIdQuery(models.Transaction);
    const updated_trans = try db.selectOne(models.Transaction, allocator, query, .{ .id = transaction.id });
    if (updated_trans) |trans| {
        try handler.returnData(trans, res);
    } else {
        handler.returnError("Internal Server Error", 500, res);
    }
    return;
}

const TransPostReq = struct {
    id: ?u32,
    amount: f64,
    type: []const u8,
    memo: ?[]const u8,
    budget_id: u32,
    created_by_user_id: u32,
    budget_category_id: ?u32,
    date: u64,
    created_at: ?u64,
    updated_at: ?u64,
    hide: u8,
};

pub fn postTransaction(req: *httpz.Request, res: *httpz.Response) !void {
    comptime {
        const postReqLen = @typeInfo(TransPostReq).Struct.fields.len;
        const transLen = @typeInfo(models.Transaction).Struct.fields.len;
        if (postReqLen != transLen) {
            @compileError(std.fmt.comptimePrint("TransactionPutReq does not equal Transaction model struct, fields inconsistent", .{}));
        }
    }
    var db = handler.getDb();
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();

    var body = handler.getReqJson(req, res, TransPostReq) catch {
        return;
    };

    const budget = try db.selectOneById(models.Budget, allocator, body.budget_id);
    if (budget == null) {
        return handler.returnError("Budget for transaction not foud or forbidden", 403, res);
    }
    _ = auth.verifyRequest(req, res, body.created_by_user_id, budget.?.family_id) catch {
        return;
    };

    const now = @intCast(u64, std.time.milliTimestamp());
    body.created_at = now;
    body.updated_at = now;

    // remove the null id field for insertion
    try db.insert(models.Transaction, utils.removeStructFields(body, &[_]u8{0}));

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

const deleteIdReq = struct {
    id: u32,
};

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

    const body = handler.getReqJson(req, res, deleteIdReq) catch {
        return;
    };

    const transaction = try db.selectOneById(models.Transaction, allocator, body.id);
    if (transaction == null) {
        return handler.returnError("Id invalid, not a transaction", 400, res);
    }

    const budget = try db.selectOneById(models.Budget, allocator, transaction.?.budget_id);
    if (budget == null) {
        return handler.returnError("Cannot find associated budget", 404, res);
    }

    _ = auth.verifyRequest(req, res, null, budget.?.family_id) catch {
        return;
    };

    try db.updateHideById(models.Transaction, true, body.id);
    const updated_transaction = try db.selectOneById(models.Transaction, allocator, body.id);
    if (updated_transaction == null) {
        return handler.returnError("Could not delete transaction", 500, res);
    }
    return try handler.returnData(updated_transaction.?, res);
}
