//! CRUD for `data` rows. Every mutation also bumps the parent raw_contact's
//! `version` / `updated_at` and the contact's `updated_at`, and records a
//! `Put` (or `Delete`) entry in `change_log` against the parent raw_contact.

use rusqlite::{ Connection, OptionalExtension, params, params_from_iter };
use rusqlite::types::Value;

use crate::db::{ ChangeKind, DbError, DbResult, now_millis, sync_state };
use crate::model::data_item::{ DataPatch, DataRow, NewDataRow, TEXT_FIELDS };

const DATA_COLUMNS: &str = "
	_id, raw_contact_id, mimetype,
	data1, data2, data3, data4, data5, data6, data7,
	data8, data9, data10, data11, data12, data13, data14, data15,
	is_primary, is_super_primary, data_version,
	sync1, sync2, sync3, sync4,
	created_at, updated_at
";

pub fn insert( conn: &mut Connection, new: NewDataRow ) -> DbResult<i64>
{
	let now = now_millis();
	let tx = conn.transaction()?;
	let raw_contact_id = new.raw_contact_id;
	let id = insert_in_tx( &tx, &new, now )?;
	sync_state::record_change( &tx, raw_contact_id, ChangeKind::Put )?;
	tx.commit()?;
	Ok( id )
}

/// In-transaction variant of [`insert`]. Used by `ApplyBatch`.
pub fn insert_in_tx(
	conn: &Connection,
	new: &NewDataRow,
	now: i64,
) -> DbResult<i64>
{
	let exists: Option<i64> = conn.query_row(
		"SELECT _id FROM raw_contacts WHERE _id = ?1 AND deleted = 0",
		params![ new.raw_contact_id ],
		|row| row.get( 0 ),
	).optional()?;
	if exists.is_none()
	{
		return Err( DbError::NotFound );
	}

	conn.execute(
		"INSERT INTO data(
			raw_contact_id, mimetype,
			data1, data2, data3, data4, data5, data6, data7,
			data8, data9, data10, data11, data12, data13, data14, data15,
			is_primary, is_super_primary,
			created_at, updated_at
		 ) VALUES(
			?1, ?2,
			?3, ?4, ?5, ?6, ?7, ?8, ?9,
			?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17,
			?18, ?19,
			?20, ?20
		 )",
		params![
			new.raw_contact_id,
			new.mimetype,
			new.fields[ 0 ],  new.fields[ 1 ],  new.fields[ 2 ],  new.fields[ 3 ],
			new.fields[ 4 ],  new.fields[ 5 ],  new.fields[ 6 ],  new.fields[ 7 ],
			new.fields[ 8 ],  new.fields[ 9 ],  new.fields[ 10 ], new.fields[ 11 ],
			new.fields[ 12 ], new.fields[ 13 ], new.blob,
			new.is_primary as i64,
			new.is_super_primary as i64,
			now,
		],
	)?;
	let id = conn.last_insert_rowid();

	bump_parents( conn, new.raw_contact_id, now )?;
	Ok( id )
}

/// Resolve `data_id` to its parent `raw_contact_id`. Returns `NotFound`
/// if the row does not exist.
pub fn raw_contact_id_of( conn: &Connection, data_id: i64 ) -> DbResult<i64>
{
	conn.query_row(
		"SELECT raw_contact_id FROM data WHERE _id = ?1",
		params![ data_id ],
		|row| row.get( 0 ),
	).optional()?.ok_or( DbError::NotFound )
}

/// Fetch only the `mimetype` of a data row, without decoding the rest.
/// Used by callers that just need the mimetype to validate an incoming
/// patch against the per-mimetype keymap.
pub fn mimetype_of( conn: &Connection, data_id: i64 ) -> DbResult<String>
{
	conn.query_row(
		"SELECT mimetype FROM data WHERE _id = ?1",
		params![ data_id ],
		|row| row.get( 0 ),
	).optional()?.ok_or( DbError::NotFound )
}

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

/// All data rows attached to `raw_contact_id`, optionally filtered by mimetype.
/// Pass `None` (or `Some("")`) to return every mimetype.
pub fn query(
	conn: &Connection,
	raw_contact_id: i64,
	mimetype: Option<&str>,
) -> DbResult<Vec<DataRow>>
{
	let mt = mimetype.filter( |m| !m.is_empty() );
	let sql = format!(
		"SELECT {DATA_COLUMNS} FROM data
		 WHERE raw_contact_id = ?1 AND ( ?2 IS NULL OR mimetype = ?2 )
		 ORDER BY _id"
	);
	let mut stmt = conn.prepare( &sql )?;
	let rows: Vec<DataRow> = stmt
		.query_map( params![ raw_contact_id, mt ], row_to_data )?
		.collect::<rusqlite::Result<_>>()?;
	Ok( rows )
}

pub fn update( conn: &mut Connection, id: i64, patch: DataPatch ) -> DbResult<()>
{
	let now = now_millis();
	let tx = conn.transaction()?;
	let raw_contact_id = update_in_tx( &tx, id, &patch, now )?;
	sync_state::record_change( &tx, raw_contact_id, ChangeKind::Put )?;
	tx.commit()?;
	Ok( () )
}

/// In-transaction variant. Returns the parent `raw_contact_id` so callers
/// can record their own change_log entries.
pub fn update_in_tx(
	conn: &Connection,
	id: i64,
	patch: &DataPatch,
	now: i64,
) -> DbResult<i64>
{
	let raw_contact_id: i64 = conn.query_row(
		"SELECT raw_contact_id FROM data WHERE _id = ?1",
		params![ id ],
		|row| row.get( 0 ),
	).optional()?.ok_or( DbError::NotFound )?;

	// Tri-state semantics on data fields:
	//   None              -> column unchanged
	//   Some( None )      -> SET column = NULL
	//   Some( Some( v ) ) -> SET column = v
	// data_version and updated_at are bumped unconditionally — even an
	// otherwise-empty patch records that the row was touched. Callers that
	// need stricter "no real change" semantics should skip the call.
	let mut set_parts: Vec<String> = Vec::new();
	let mut vals: Vec<Value> = Vec::new();

	for ( i, slot ) in patch.fields.iter().enumerate()
	{
		if let Some( v ) = slot
		{
			set_parts.push( format!( "data{} = ?", i + 1 ) );
			vals.push( match v
			{
				Some( s ) => Value::Text( s.clone() ),
				None      => Value::Null,
			} );
		}
	}
	if let Some( v ) = &patch.blob
	{
		set_parts.push( format!( "data{} = ?", TEXT_FIELDS + 1 ) );
		vals.push( match v
		{
			Some( bytes ) => Value::Blob( bytes.clone() ),
			None          => Value::Null,
		} );
	}
	if let Some( b ) = patch.is_primary
	{
		set_parts.push( "is_primary = ?".into() );
		vals.push( Value::Integer( b as i64 ) );
	}
	if let Some( b ) = patch.is_super_primary
	{
		set_parts.push( "is_super_primary = ?".into() );
		vals.push( Value::Integer( b as i64 ) );
	}

	set_parts.push( "data_version = data_version + 1".into() );
	set_parts.push( "updated_at = ?".into() );
	vals.push( Value::Integer( now ) );

	vals.push( Value::Integer( id ) );

	let sql = format!(
		"UPDATE data SET {} WHERE _id = ?",
		set_parts.join( ", " ),
	);

	let affected = conn.execute( &sql, params_from_iter( vals.iter() ) )?;
	if affected == 0
	{
		return Err( DbError::NotFound );
	}

	bump_parents( conn, raw_contact_id, now )?;
	Ok( raw_contact_id )
}

pub fn delete( conn: &mut Connection, id: i64 ) -> DbResult<()>
{
	let now = now_millis();
	let tx = conn.transaction()?;
	let raw_contact_id = delete_in_tx( &tx, id, now )?;
	sync_state::record_change( &tx, raw_contact_id, ChangeKind::Put )?;
	tx.commit()?;
	Ok( () )
}

/// In-transaction variant. Returns the parent `raw_contact_id`.
pub fn delete_in_tx( conn: &Connection, id: i64, now: i64 ) -> DbResult<i64>
{
	let raw_contact_id: Option<i64> = conn.query_row(
		"SELECT raw_contact_id FROM data WHERE _id = ?1",
		params![ id ],
		|row| row.get( 0 ),
	).optional()?;
	let raw_contact_id = raw_contact_id.ok_or( DbError::NotFound )?;

	conn.execute( "DELETE FROM data WHERE _id = ?1", params![ id ] )?;

	bump_parents( conn, raw_contact_id, now )?;
	Ok( raw_contact_id )
}

/// Phone-number lookup mirroring Android `PhoneLookup` semantics: match on
/// the last 7 digits of `number` against `data4` (E.164 normalised) of any
/// `phone_v2` row. Returns the parent `raw_contact_id`s (deduplicated).
pub fn search_by_normalized_number(
	conn: &Connection,
	number: &str,
) -> DbResult<Vec<i64>>
{
	let digits: String = number.chars().filter( |c| c.is_ascii_digit() ).collect();
	let suffix: &str = if digits.len() > 7
	{
		&digits[ digits.len() - 7.. ]
	} else {
		digits.as_str()
	};
	let pattern = format!( "%{}", suffix );

	let mut stmt = conn.prepare(
		"SELECT DISTINCT raw_contact_id FROM data
		 WHERE mimetype = ?1 AND data4 LIKE ?2"
	)?;
	let ids: Vec<i64> = stmt
		.query_map(
			params![ crate::model::mimetype::PHONE_V2, pattern ],
			|row| row.get( 0 ),
		)?
		.collect::<rusqlite::Result<_>>()?;
	Ok( ids )
}

fn bump_parents( conn: &Connection, raw_contact_id: i64, now: i64 ) -> DbResult<()>
{
	conn.execute(
		"UPDATE raw_contacts SET version = version + 1, updated_at = ?2 WHERE _id = ?1",
		params![ raw_contact_id, now ],
	)?;
	conn.execute(
		"UPDATE contacts SET updated_at = ?2
		 WHERE _id = ( SELECT contact_id FROM raw_contacts WHERE _id = ?1 )",
		params![ raw_contact_id, now ],
	)?;
	Ok( () )
}

fn row_to_data( row: &rusqlite::Row<'_> ) -> rusqlite::Result<DataRow>
{
	let fields: [ Option<String>; TEXT_FIELDS ] = [
		row.get( 3 )?,  row.get( 4 )?,  row.get( 5 )?,  row.get( 6 )?,
		row.get( 7 )?,  row.get( 8 )?,  row.get( 9 )?,  row.get( 10 )?,
		row.get( 11 )?, row.get( 12 )?, row.get( 13 )?, row.get( 14 )?,
		row.get( 15 )?, row.get( 16 )?,
	];
	let sync: [ Option<String>; 4 ] = [
		row.get( 21 )?, row.get( 22 )?, row.get( 23 )?, row.get( 24 )?,
	];
	Ok( DataRow {
		id:               row.get( 0 )?,
		raw_contact_id:   row.get( 1 )?,
		mimetype:         row.get( 2 )?,
		fields,
		blob:             row.get( 17 )?,
		is_primary:       row.get::<_, i64>( 18 )? != 0,
		is_super_primary: row.get::<_, i64>( 19 )? != 0,
		data_version:     row.get( 20 )?,
		sync,
		created_at:       row.get( 25 )?,
		updated_at:       row.get( 26 )?,
	} )
}
