const std = @import("std");
const c = @cImport({
    @cInclude("sqlite3.h");
});

const Highlight = struct {
    text: []const u8, // Owned copy
    date: []const u8, // Owned copy
    book: []const u8, // Owned copy
    text_hash: [64]u8, // Stack allocated - no cleanup needed
    book_hash: [64]u8, // Stack allocated - no cleanup needed
    allocator: std.mem.Allocator,

    fn init(allocator: std.mem.Allocator, text: []const u8, date: []const u8, book: []const u8, content_id_raw: []const u8) !Highlight {
        var highlight = Highlight{
            .text = try allocator.dupe(u8, text), // Copy from SQLite buffer
            .date = try allocator.dupe(u8, date), // Copy from SQLite buffer
            .book = try allocator.dupe(u8, book), // Copy from SQLite buffer
            .text_hash = undefined,
            .book_hash = undefined,
            .allocator = allocator,
        };

        // Now compute hashes into the struct fields
        computeHash(text, &highlight.text_hash);
        computeHash(content_id_raw, &highlight.book_hash);

        return highlight;
    }

    fn deinit(self: *Highlight) void {
        self.allocator.free(self.text);
        self.allocator.free(self.date);
        self.allocator.free(self.book);
    }

    fn computeHash(data: []const u8, out_hex: *[64]u8) void {
        var hash: [32]u8 = undefined; // SHA256 produces 32 bytes
        std.crypto.hash.sha2.Sha256.hash(data, &hash, .{});

        // Convert 32 bytes to 64 hex characters
        _ = std.fmt.bufPrint(out_hex, "{s}", .{std.fmt.fmtSliceHexLower(&hash)}) catch unreachable;
    }
};

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Get command line args
    const args = try std.process.argsAlloc(allocator);
    defer std.process.argsFree(allocator, args);

    if (args.len < 2) {
        std.debug.print("Usage: {s} <path-to-kobo-database.sqlite>\n", .{args[0]});
        std.debug.print("Example: {s} /mnt/kobo/.kobo/KoboReader.sqlite\n", .{args[0]});
        return error.MissingArgument;
    }

    const db_path = args[1];

    // Open database
    var db: ?*c.sqlite3 = null;
    const rc = c.sqlite3_open(db_path.ptr, &db);
    if (rc != c.SQLITE_OK) {
        std.debug.print("Failed to open database: {s}\n", .{c.sqlite3_errmsg(db)});
        return error.DatabaseOpenFailed;
    }
    defer _ = c.sqlite3_close(db);

    // Prepare query to get highlights
    const query =
        \\SELECT
        \\  Text,
        \\  DateCreated,
        \\  SUBSTR(
        \\    SUBSTR(ContentID, 40, INSTR(ContentID, '.epub') - 40 + 5),
        \\    INSTR(SUBSTR(ContentID, 40, INSTR(ContentID, '.epub') - 40 + 5), '/') + 1
        \\  ) AS Book
        \\FROM Bookmark
        \\WHERE Text IS NOT NULL
        \\ORDER BY DateCreated DESC;
    ;

    var stmt: ?*c.sqlite3_stmt = null;
    if (c.sqlite3_prepare_v2(db, query.ptr, @intCast(query.len), &stmt, null) != c.SQLITE_OK) {
        std.debug.print("Failed to prepare statement: {s}\n", .{c.sqlite3_errmsg(db)});
        return error.QueryPrepareFailed;
    }
    defer _ = c.sqlite3_finalize(stmt);

    // Set up buffered stdout writer
    const stdout_file = std.fs.File{ .handle = std.posix.STDOUT_FILENO };
    var stdout_buffer: [4096]u8 = undefined;
    var stdout_writer = stdout_file.writer(&stdout_buffer);
    const stdout = &stdout_writer.interface;
    var count: usize = 0;

    // Execute query and print results
    while (c.sqlite3_step(stmt) == c.SQLITE_ROW) {
        count += 1;

        // Get the text (highlight)
        const text_ptr = c.sqlite3_column_text(stmt, 0);
        const text = if (text_ptr != null)
            std.mem.span(@as([*:0]const u8, @ptrCast(text_ptr)))
        else
            "";

        // Get date
        const date_ptr = c.sqlite3_column_text(stmt, 1);
        const date = if (date_ptr != null)
            std.mem.span(@as([*:0]const u8, @ptrCast(date_ptr)))
        else
            "unknown";

        // Get book title
        const book_ptr = c.sqlite3_column_text(stmt, 2);
        const book = if (book_ptr != null)
            std.mem.span(@as([*:0]const u8, @ptrCast(book_ptr)))
        else
            "unknown";

        // Print formatted output
        try stdout.print("\n========== Highlight #{d} ==========\n", .{count});
        try stdout.print("Date: {s}\n", .{date});
        try stdout.print("Book: {s}\n", .{book});
        try stdout.print("\n{s}\n", .{text});
        try stdout.print("===================================\n", .{});
    }

    try stdout.print("\nTotal highlights extracted: {d}\n", .{count});
    try stdout_writer.interface.flush();
}
