//! Shared reassign operations - move a source record from one entity to another.
//!
//! Reassignment moves a single identity_link from one canonical entity to
//! another, recomputes both entities' canonical data, and emits events.

use serde::Serialize;
use sqlx::PgConnection;
use uuid::Uuid;

use super::merge::recompute_canonical_data;

/// Request to reassign a source record to a different canonical entity.
pub struct ReassignRequest {
    pub tenant_id: Uuid,
    /// The external entity (source record) to move.
    pub external_entity_id: Uuid,
    /// The canonical entity it currently belongs to.
    pub from_canonical_id: Uuid,
    /// The canonical entity to move it to.
    pub to_canonical_id: Uuid,
    /// Arbitrary metadata included in the entity_events payload.
    pub metadata: serde_json::Value,
}

/// Result of a successful reassignment.
#[derive(Debug, Serialize)]
pub struct ReassignResult {
    pub external_entity_id: Uuid,
    pub from_canonical_id: Uuid,
    pub to_canonical_id: Uuid,
}

/// Execute a reassignment of a source record from one canonical entity to another.
///
/// Steps:
/// 1. Verify identity_link exists for (external_entity_id, from_canonical_id)
/// 2. Verify target canonical entity exists
/// 3. UPDATE identity_links SET canonical_entity_id = to_canonical_id
/// 4. Emit entity.updated events on both source and target
/// 5. Recompute canonical_data on both entities
pub async fn execute_reassign(
    conn: &mut PgConnection,
    req: &ReassignRequest,
) -> crate::error::Result<ReassignResult> {
    // 1. Verify identity_link exists for (external_entity_id, from_canonical_id)
    let link_exists: bool = sqlx::query_scalar!(
        "SELECT EXISTS(\
            SELECT 1 FROM identity_links \
            WHERE external_entity_id = $1 AND canonical_entity_id = $2 AND tenant_id = $3\
        ) as \"exists!: bool\"",
        req.external_entity_id,
        req.from_canonical_id,
        req.tenant_id,
    )
    .fetch_one(&mut *conn)
    .await?;

    if !link_exists {
        return Err(crate::error::CannonError::NotFound(format!(
            "No identity link for external entity {} on canonical entity {}",
            req.external_entity_id, req.from_canonical_id
        )));
    }

    // 2. Verify target canonical entity exists
    let target_exists: bool = sqlx::query_scalar!(
        "SELECT EXISTS(\
            SELECT 1 FROM canonical_entities WHERE id = $1 AND tenant_id = $2\
        ) as \"exists!: bool\"",
        req.to_canonical_id,
        req.tenant_id,
    )
    .fetch_one(&mut *conn)
    .await?;

    if !target_exists {
        return Err(crate::error::CannonError::NotFound(format!(
            "Target canonical entity {} not found",
            req.to_canonical_id
        )));
    }

    // 3. UPDATE identity_links
    sqlx::query!(
        "UPDATE identity_links SET canonical_entity_id = $1 \
         WHERE external_entity_id = $2 AND tenant_id = $3",
        req.to_canonical_id,
        req.external_entity_id,
        req.tenant_id,
    )
    .execute(&mut *conn)
    .await?;

    // 4. Emit entity.updated events on both entities
    let mut event_payload = serde_json::json!({
        "reassigned_member": req.external_entity_id.to_string(),
        "from_entity": req.from_canonical_id.to_string(),
        "to_entity": req.to_canonical_id.to_string(),
    });
    if let Some(obj) = req.metadata.as_object() {
        for (k, v) in obj {
            event_payload[k] = v.clone();
        }
    }

    // Event on source entity (member removed)
    let idem_key_from = format!(
        "reassign-from:{}:{}:{}",
        req.from_canonical_id, req.external_entity_id, req.to_canonical_id
    );
    sqlx::query!(
        "INSERT INTO entity_events (tenant_id, entity_id, event_type, payload, idempotency_key) \
         VALUES ($1, $2, 'entity.updated', $3, $4) \
         ON CONFLICT (tenant_id, idempotency_key) WHERE idempotency_key IS NOT NULL DO NOTHING",
        req.tenant_id,
        req.from_canonical_id,
        &event_payload,
        &idem_key_from,
    )
    .execute(&mut *conn)
    .await?;

    // Event on target entity (member added)
    let idem_key_to = format!(
        "reassign-to:{}:{}:{}",
        req.to_canonical_id, req.external_entity_id, req.from_canonical_id
    );
    sqlx::query!(
        "INSERT INTO entity_events (tenant_id, entity_id, event_type, payload, idempotency_key) \
         VALUES ($1, $2, 'entity.updated', $3, $4) \
         ON CONFLICT (tenant_id, idempotency_key) WHERE idempotency_key IS NOT NULL DO NOTHING",
        req.tenant_id,
        req.to_canonical_id,
        &event_payload,
        &idem_key_to,
    )
    .execute(&mut *conn)
    .await?;

    // 5. Recompute canonical_data on both entities
    recompute_canonical_data(&mut *conn, req.tenant_id, req.from_canonical_id).await?;
    recompute_canonical_data(&mut *conn, req.tenant_id, req.to_canonical_id).await?;

    Ok(ReassignResult {
        external_entity_id: req.external_entity_id,
        from_canonical_id: req.from_canonical_id,
        to_canonical_id: req.to_canonical_id,
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_reassign_result_serialize() {
        let result = ReassignResult {
            external_entity_id: Uuid::nil(),
            from_canonical_id: Uuid::nil(),
            to_canonical_id: Uuid::nil(),
        };
        let json = serde_json::to_value(&result).unwrap();
        assert!(json.get("external_entity_id").is_some());
        assert!(json.get("from_canonical_id").is_some());
        assert!(json.get("to_canonical_id").is_some());
    }

    #[test]
    fn test_reassign_result_field_values() {
        let ext = Uuid::parse_str("aaaaaaaa-1111-2222-3333-444444444444").unwrap();
        let from = Uuid::parse_str("bbbbbbbb-5555-6666-7777-888888888888").unwrap();
        let to = Uuid::parse_str("cccccccc-9999-aaaa-bbbb-cccccccccccc").unwrap();

        let result = ReassignResult {
            external_entity_id: ext,
            from_canonical_id: from,
            to_canonical_id: to,
        };
        let json = serde_json::to_value(&result).unwrap();
        assert_eq!(
            json["external_entity_id"].as_str().unwrap(),
            "aaaaaaaa-1111-2222-3333-444444444444"
        );
        assert_eq!(
            json["from_canonical_id"].as_str().unwrap(),
            "bbbbbbbb-5555-6666-7777-888888888888"
        );
        assert_eq!(
            json["to_canonical_id"].as_str().unwrap(),
            "cccccccc-9999-aaaa-bbbb-cccccccccccc"
        );
    }

    #[test]
    fn test_reassign_request_fields() {
        // Verify ReassignRequest can be constructed with all fields
        let req = ReassignRequest {
            tenant_id: Uuid::nil(),
            external_entity_id: Uuid::nil(),
            from_canonical_id: Uuid::nil(),
            to_canonical_id: Uuid::nil(),
            metadata: serde_json::json!({"reason": "test reassignment"}),
        };
        assert_eq!(req.metadata["reason"], "test reassignment");
    }
}
