//! SQLite persistence layer.
//!
//! Operations are synchronous (rusqlite is sync). Async callers wrap them
//! with `smol::unblock` to avoid stalling the executor. A single locked
//! connection per daemon instance is enough for our write rates, and WAL
//! mode keeps reads lock-free against in-flight writers.

use std::path::Path;
use std::sync::{ Mutex, MutexGuard };
use std::time::{ SystemTime, UNIX_EPOCH };

use rusqlite::Connection;
use thiserror::Error;

pub mod schema;
pub mod calls;

#[ derive( Debug, Error ) ]
pub enum DbError
{
	#[ error( "sqlite: {0}" ) ]
	Sqlite( #[ from ] rusqlite::Error ),

	#[ error( "not found" ) ]
	NotFound,
}

pub type DbResult<T> = Result<T, DbError>;

pub struct Database
{
	conn: Mutex<Connection>,
}

impl Database
{
	pub fn open( path: &Path ) -> DbResult<Self>
	{
		let mut conn = Connection::open( path )?;
		configure_pragmas( &conn )?;
		schema::apply_migrations( &mut conn )?;
		Ok( Self { conn: Mutex::new( conn ) } )
	}

	pub fn open_in_memory() -> DbResult<Self>
	{
		let mut conn = Connection::open_in_memory()?;
		configure_pragmas( &conn )?;
		schema::apply_migrations( &mut conn )?;
		Ok( Self { conn: Mutex::new( conn ) } )
	}

	/// Borrow the underlying connection for the duration of `f`. The closure
	/// runs while the mutex is held — keep it short.
	pub fn with_conn<F, R>( &self, f: F ) -> R
	where
		F: FnOnce( &mut Connection ) -> R,
	{
		let mut guard = self.lock();
		f( &mut guard )
	}

	fn lock( &self ) -> MutexGuard<'_, Connection>
	{
		// A poisoned mutex means a previous holder panicked inside `with_conn`.
		// The DB itself is not corrupted (rusqlite uses its own locking), so we
		// recover the inner guard rather than abort.
		match self.conn.lock()
		{
			Ok( g )  => g,
			Err( p ) => p.into_inner(),
		}
	}
}

fn configure_pragmas( conn: &Connection ) -> DbResult<()>
{
	conn.pragma_update( None, "journal_mode", "WAL" )?;
	conn.pragma_update( None, "synchronous",  "NORMAL" )?;
	conn.pragma_update( None, "foreign_keys", "ON" )?;
	Ok( () )
}

/// Milliseconds since the Unix epoch. Used for `created_at`.
pub fn now_millis() -> i64
{
	SystemTime::now()
		.duration_since( UNIX_EPOCH )
		.map( |d| d.as_millis() as i64 )
		.unwrap_or( 0 )
}
