//! Building blocks shared by PROPFIND, REPORT and direct GET handlers.
//!
//! Each `ContactSnapshot` represents a single CardDAV resource: serialized
//! vCard bytes + ETag + Last-Modified, ready to slot into either an
//! `address-data` element or an HTTP body. Snapshots are built on demand
//! and never cached — Phase 4 does not need it; we revisit caching when
//! the contact count grows past low thousands.

use std::sync::Arc;

use crate::carddav::etag;
use crate::carddav::vcard_serialize::{ self, VCardInput };
use crate::carddav::CardDavResult;
use crate::db::{ self, Database };

#[ derive( Debug ) ]
pub struct ContactSnapshot
{
	pub raw_contact_id: i64,
	pub uid: String,
	pub updated_at: i64,
	pub vcard_body: Vec<u8>,
	pub etag: String,
	pub last_modified: String,
}

#[ derive( Debug ) ]
pub struct AddressbookSnapshot
{
	pub change_counter: i64,
	pub contacts: Vec<ContactSnapshot>,
}

/// Full snapshot of every non-deleted contact, plus the current
/// `change_counter` (used for `getctag` / `sync-token`).
pub async fn full_addressbook( db: Arc<Database> ) -> CardDavResult<AddressbookSnapshot>
{
	let snapshot = smol::unblock( move ||
	{
		db.with_conn( |conn| -> db::DbResult<AddressbookSnapshot>
		{
			let change_counter = db::sync_state::change_counter( conn )?;
			let entries = db::raw_contacts::list_for_carddav( conn )?;
			let mut contacts = Vec::with_capacity( entries.len() );
			for ( raw_id, uid, _updated_at ) in entries
			{
				contacts.push( build_one( conn, raw_id, uid )? );
			}
			Ok( AddressbookSnapshot { change_counter, contacts } )
		} )
	} ).await?;
	Ok( snapshot )
}

/// Snapshot for a single resource, located by its `carddav_uid`. Soft-deleted
/// raw_contacts surface as `CardDavError::NotFound` (clients see a 404 for
/// the resource even though the row still lives in the database).
pub async fn by_uid( db: Arc<Database>, uid: String ) -> CardDavResult<ContactSnapshot>
{
	let entry = smol::unblock( move ||
	{
		db.with_conn( |c| -> db::DbResult<_>
		{
			let raw_id = db::raw_contacts::id_by_carddav_uid( c, &uid )?;
			build_one( c, raw_id, uid )
		} )
	} ).await?;
	Ok( entry )
}

pub fn build_one(
	conn: &mut rusqlite::Connection,
	raw_contact_id: i64,
	uid: String,
) -> db::DbResult<ContactSnapshot>
{
	let raw = db::raw_contacts::get( conn, raw_contact_id )?;
	let data_rows = db::data::query( conn, raw_contact_id, None )?;
	let body = vcard_serialize::serialize( VCardInput
	{
		uid:         &uid,
		raw_contact: &raw,
		data_rows:   &data_rows,
	} );
	let etag = etag::etag_for( &body );
	let last_modified = format_http_date( raw.updated_at );
	Ok( ContactSnapshot
	{
		raw_contact_id,
		uid,
		updated_at: raw.updated_at,
		vcard_body: body,
		etag,
		last_modified,
	} )
}

/// Format `ms_since_epoch` as an RFC 1123 date suitable for a
/// `Last-Modified` header or `<getlastmodified>` element.
pub fn format_http_date( ms: i64 ) -> String
{
	use chrono::TimeZone;
	let secs = ms.div_euclid( 1000 );
	match chrono::Utc.timestamp_opt( secs, 0 )
	{
		chrono::LocalResult::Single( dt ) =>
			dt.format( "%a, %d %b %Y %H:%M:%S GMT" ).to_string(),
		_ => "Thu, 01 Jan 1970 00:00:00 GMT".to_owned(),
	}
}
