#include "DatabaseManager.hpp"
#include <stdexcept>
#include <chrono>
#include <cstring>   // std::strlen
#include <sys/stat.h>

namespace {
  inline std::int64_t file_size(const std::string& p) {
    struct stat st{};
    return ::stat(p.c_str(), &st) == 0 ? static_cast<std::int64_t>(st.st_size) : 0;
  }
  inline std::int64_t file_mtime(const std::string& p) {
    struct stat st{};
    return ::stat(p.c_str(), &st) == 0 ? static_cast<std::int64_t>(st.st_mtim.tv_sec) : 0;
  }
}

DatabaseManager::DatabaseManager(std::string db_path)
  : db_path_(std::move(db_path)) {
  if (sqlite3_open(db_path_.c_str(), &db_) != SQLITE_OK) {
    throw std::runtime_error(std::string("sqlite open failed: ") + sqlite3_errmsg(db_));
  }
  sqlite3_busy_handler(db_, &DatabaseManager::busy_handler, this);
  ensure_schema();
}

DatabaseManager::~DatabaseManager() {
  // if (stmt_upsert_) sqlite3_finalize(stmt_upsert_);
  // …
  if (db_) sqlite3_close(db_);
}

int DatabaseManager::busy_handler(void*, int prev_ms) {
  // simple backoff up to ~1s total
  int sleep_ms = std::min(prev_ms + 50, 200);
  sqlite3_sleep(sleep_ms);
  return 1; // retry
}

void DatabaseManager::ensure_schema() {
  std::lock_guard lk(mtx_);
  static const char* ddl =
    "BEGIN;"
    "CREATE TABLE IF NOT EXISTS books ("
    " id TEXT PRIMARY KEY,"
    " title TEXT NOT NULL,"
    " author TEXT NOT NULL DEFAULT '',"
    " file_path TEXT NOT NULL,"
    " cover_path TEXT NOT NULL DEFAULT '',"
    " size_bytes INTEGER NOT NULL DEFAULT 0,"
    " mtime_unix INTEGER NOT NULL DEFAULT 0,"
    " added_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))"
    ");"
    "CREATE INDEX IF NOT EXISTS idx_books_title ON books(title);"
    "CREATE TABLE IF NOT EXISTS tags ("
    " id INTEGER PRIMARY KEY AUTOINCREMENT,"
    " name TEXT NOT NULL UNIQUE"
    ");"
    "CREATE TABLE IF NOT EXISTS book_tags ("
    " book_id TEXT NOT NULL,"
    " tag_id INTEGER NOT NULL,"
    " FOREIGN KEY (book_id) REFERENCES books(id) ON DELETE CASCADE,"
    " FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE,"
    " PRIMARY KEY (book_id, tag_id)"
    ");"
    "COMMIT;";
  char* err = nullptr;
  if (sqlite3_exec(db_, ddl, nullptr, nullptr, &err) != SQLITE_OK) {
    std::string msg = err ? err : "unknown sqlite error";
    sqlite3_free(err);
    throw std::runtime_error("schema failed: " + msg);
  }
}

bool DatabaseManager::upsert_book(const Book& b) {
  std::lock_guard lk(mtx_);
  static const char* sql =
    "INSERT INTO books(id,title,author,file_path,cover_path,size_bytes,mtime_unix)"
    " VALUES(?,?,?,?,?,?,?)"
    " ON CONFLICT(id) DO UPDATE SET"
    "  title=excluded.title,"
    "  author=excluded.author,"
    "  file_path=excluded.file_path,"
    "  cover_path=excluded.cover_path,"
    "  size_bytes=excluded.size_bytes,"
    "  mtime_unix=excluded.mtime_unix;";
  sqlite3_stmt* st = nullptr;
  if (sqlite3_prepare_v2(db_, sql, -1, &st, nullptr) != SQLITE_OK) return false;

  sqlite3_bind_text(st, 1, b.id().c_str(),        -1, SQLITE_TRANSIENT);
  sqlite3_bind_text(st, 2, b.title().c_str(),     -1, SQLITE_TRANSIENT);
  sqlite3_bind_text(st, 3, b.author().c_str(),    -1, SQLITE_TRANSIENT);
  sqlite3_bind_text(st, 4, b.filePath().c_str(),  -1, SQLITE_TRANSIENT);
  sqlite3_bind_text(st, 5, b.coverPath().c_str(), -1, SQLITE_TRANSIENT);
  sqlite3_bind_int64(st, 6, file_size(b.filePath()));
  sqlite3_bind_int64(st, 7, file_mtime(b.filePath()));

  bool ok = (sqlite3_step(st) == SQLITE_DONE);
  sqlite3_finalize(st);
  return ok;
}

bool DatabaseManager::remove_book(const std::string& id) {
  std::lock_guard lk(mtx_);
  static const char* sql = "DELETE FROM books WHERE id = ?;";
  sqlite3_stmt* st = nullptr;
  if (sqlite3_prepare_v2(db_, sql, -1, &st, nullptr) != SQLITE_OK) return false;
  sqlite3_bind_text(st, 1, id.c_str(), -1, SQLITE_TRANSIENT);
  bool ok = (sqlite3_step(st) == SQLITE_DONE);
  sqlite3_finalize(st);
  return ok;
}

std::optional<Book> DatabaseManager::get_book(const std::string& id) {
  std::lock_guard lk(mtx_);
  static const char* sql =
    "SELECT id,title,author,file_path,cover_path FROM books WHERE id = ?;";
  sqlite3_stmt* st = nullptr;
  if (sqlite3_prepare_v2(db_, sql, -1, &st, nullptr) != SQLITE_OK) return std::nullopt;
  sqlite3_bind_text(st, 1, id.c_str(), -1, SQLITE_TRANSIENT);

  std::optional<Book> out;
  if (sqlite3_step(st) == SQLITE_ROW) {
    Book b(
      reinterpret_cast<const char*>(sqlite3_column_text(st,0)),
      reinterpret_cast<const char*>(sqlite3_column_text(st,1)),
      reinterpret_cast<const char*>(sqlite3_column_text(st,2)),
      reinterpret_cast<const char*>(sqlite3_column_text(st,3)),
      reinterpret_cast<const char*>(sqlite3_column_text(st,4))
    );
    out = std::move(b);
  }
  sqlite3_finalize(st);
  return out;
}

std::vector<Book> DatabaseManager::load_all_books() {
  std::lock_guard lk(mtx_);
  static const char* sql =
    "SELECT id,title,author,file_path,cover_path FROM books ORDER BY title,author,id;";
  sqlite3_stmt* st = nullptr;
  std::vector<Book> rows;

  if (sqlite3_prepare_v2(db_, sql, -1, &st, nullptr) != SQLITE_OK) return rows;

  while (sqlite3_step(st) == SQLITE_ROW) {
    Book b(
      reinterpret_cast<const char*>(sqlite3_column_text(st,0)),
      reinterpret_cast<const char*>(sqlite3_column_text(st,1)),
      reinterpret_cast<const char*>(sqlite3_column_text(st,2)),
      reinterpret_cast<const char*>(sqlite3_column_text(st,3)),
      reinterpret_cast<const char*>(sqlite3_column_text(st,4))
    );
    b.set_tags(get_tags_for_book(b.id()));
    rows.push_back(std::move(b));
  }
  sqlite3_finalize(st);
  return rows;
}

bool DatabaseManager::add_tag_to_book(const std::string& book_id, const std::string& tag_name) {
  std::lock_guard lk(mtx_);
  static const char* sql =
    "INSERT OR IGNORE INTO tags (name) VALUES (?);"
    "INSERT OR IGNORE INTO book_tags (book_id, tag_id) VALUES (?, (SELECT id FROM tags WHERE name = ?));";
  sqlite3_stmt* st = nullptr;
  if (sqlite3_prepare_v2(db_, sql, -1, &st, nullptr) != SQLITE_OK) return false;

  sqlite3_bind_text(st, 1, tag_name.c_str(), -1, SQLITE_TRANSIENT);
  sqlite3_bind_text(st, 2, book_id.c_str(), -1, SQLITE_TRANSIENT);
  sqlite3_bind_text(st, 3, tag_name.c_str(), -1, SQLITE_TRANSIENT);

  bool ok = (sqlite3_step(st) == SQLITE_DONE);
  sqlite3_finalize(st);
  return ok;
}

bool DatabaseManager::remove_tag_from_book(const std::string& book_id, const std::string& tag_name) {
  std::lock_guard lk(mtx_);
  static const char* sql =
    "DELETE FROM book_tags WHERE book_id = ? AND tag_id = (SELECT id FROM tags WHERE name = ?);";
  sqlite3_stmt* st = nullptr;
  if (sqlite3_prepare_v2(db_, sql, -1, &st, nullptr) != SQLITE_OK) return false;

  sqlite3_bind_text(st, 1, book_id.c_str(), -1, SQLITE_TRANSIENT);
  sqlite3_bind_text(st, 2, tag_name.c_str(), -1, SQLITE_TRANSIENT);

  bool ok = (sqlite3_step(st) == SQLITE_DONE);
  sqlite3_finalize(st);
  return ok;
}

std::vector<std::string> DatabaseManager::get_tags_for_book(const std::string& book_id) {
  std::lock_guard lk(mtx_);
  static const char* sql =
    "SELECT name FROM tags WHERE id IN (SELECT tag_id FROM book_tags WHERE book_id = ?);";
  sqlite3_stmt* st = nullptr;
  std::vector<std::string> rows;

  if (sqlite3_prepare_v2(db_, sql, -1, &st, nullptr) != SQLITE_OK) return rows;

  sqlite3_bind_text(st, 1, book_id.c_str(), -1, SQLITE_TRANSIENT);

  while (sqlite3_step(st) == SQLITE_ROW) {
    rows.emplace_back(
      reinterpret_cast<const char*>(sqlite3_column_text(st,0))
    );
  }
  sqlite3_finalize(st);
  return rows;
}

