//! CRUD for the `calls` table.

use rusqlite::{ Connection, OptionalExtension, params };

use crate::db::{ DbError, DbResult, now_millis };
use crate::model::{ Call, NewCall };

const CALL_COLUMNS: &str = "_id, number, date, duration, type, new, cached_name, created_at";

pub fn insert( conn: &Connection, new: NewCall ) -> DbResult<i64>
{
	let now = now_millis();
	conn.execute(
		"INSERT INTO calls( number, date, duration, type, new, cached_name, created_at )
		 VALUES( ?1, ?2, ?3, ?4, 1, ?5, ?6 )",
		params![ new.number, new.date, new.duration, new.call_type, new.cached_name, now ],
	)?;
	Ok( conn.last_insert_rowid() )
}

/// Most recent calls, newest-first. `limit <= 0` returns every row.
pub fn recent( conn: &Connection, limit: i64 ) -> DbResult<Vec<Call>>
{
	let base = format!( "SELECT {CALL_COLUMNS} FROM calls ORDER BY date DESC, _id DESC" );
	if limit > 0
	{
		let sql = format!( "{base} LIMIT ?1" );
		let mut stmt = conn.prepare( &sql )?;
		let rows = stmt.query_map( params![ limit ], row_to_call )?
			.collect::<rusqlite::Result<_>>()?;
		Ok( rows )
	} else {
		let mut stmt = conn.prepare( &base )?;
		let rows = stmt.query_map( [], row_to_call )?
			.collect::<rusqlite::Result<_>>()?;
		Ok( rows )
	}
}

pub fn get( conn: &Connection, id: i64 ) -> DbResult<Call>
{
	let sql = format!( "SELECT {CALL_COLUMNS} FROM calls WHERE _id = ?1" );
	conn.query_row( &sql, params![ id ], row_to_call )
		.optional()?
		.ok_or( DbError::NotFound )
}

pub fn delete( conn: &Connection, id: i64 ) -> DbResult<()>
{
	let affected = conn.execute( "DELETE FROM calls WHERE _id = ?1", params![ id ] )?;
	if affected == 0
	{
		return Err( DbError::NotFound );
	}
	Ok( () )
}

pub fn delete_all( conn: &Connection ) -> DbResult<usize>
{
	Ok( conn.execute( "DELETE FROM calls", [] )? )
}

pub fn mark_all_read( conn: &Connection ) -> DbResult<usize>
{
	Ok( conn.execute( "UPDATE calls SET new = 0 WHERE new = 1", [] )? )
}

fn row_to_call( row: &rusqlite::Row<'_> ) -> rusqlite::Result<Call>
{
	Ok( Call {
		id:          row.get( 0 )?,
		number:      row.get( 1 )?,
		date:        row.get( 2 )?,
		duration:    row.get( 3 )?,
		call_type:   row.get( 4 )?,
		new:         row.get::<_, i64>( 5 )? != 0,
		cached_name: row.get( 6 )?,
		created_at:  row.get( 7 )?,
	} )
}

#[ cfg( test ) ]
mod tests
{
	use super::*;
	use crate::db::Database;
	use crate::model::call_type;

	#[ test ]
	fn insert_query_delete_roundtrip()
	{
		let db = Database::open_in_memory().expect( "open" );

		db.with_conn( |c| -> DbResult<()>
		{
			let a = insert( c, NewCall {
				number: "+34600111222".into(),
				date: 1_000,
				duration: 0,
				call_type: call_type::MISSED,
				cached_name: None,
			} )?;
			let b = insert( c, NewCall {
				number: "+34600333444".into(),
				date: 2_000,
				duration: 42,
				call_type: call_type::OUTGOING,
				cached_name: Some( "Ada".into() ),
			} )?;

			// Newest-first ordering by date.
			let recents = recent( c, 10 )?;
			assert_eq!( recents.len(), 2 );
			assert_eq!( recents[ 0 ].id, b );
			assert_eq!( recents[ 1 ].id, a );
			assert_eq!( recents[ 0 ].cached_name.as_deref(), Some( "Ada" ) );
			assert!( recents[ 0 ].new );

			assert_eq!( mark_all_read( c )?, 2 );
			assert!( !recent( c, 1 )?[ 0 ].new );

			delete( c, a )?;
			assert!( matches!( get( c, a ), Err( DbError::NotFound ) ) );
			assert_eq!( recent( c, 10 )?.len(), 1 );

			assert_eq!( delete_all( c )?, 1 );
			assert!( recent( c, 10 )?.is_empty() );
			Ok( () )
		} ).expect( "roundtrip" );
	}
}
