//! Shared split operations - single source of truth for entity splits.
//!
//! Every split in the system (auto_split agent, manual API, CLI) MUST go
//! through `execute_split()`. This ensures consistent behavior:
//! - ejected members get new canonical entities
//! - identity_links are moved to new entities
//! - entity_events event is emitted (downstream systems)
//! - canonical_data is recomputed on both original and new entities

use serde::{Deserialize, Serialize};
use sqlx::PgConnection;
use uuid::Uuid;

use super::merge::recompute_canonical_data;

/// The type/source of a split operation.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SplitType {
    /// Batch reconciliation pipeline (coherence-aware)
    Auto,
    /// Agent-initiated split (auto_split agent)
    Agent,
    /// Manual split (API, CLI, admin action)
    Manual,
}

impl SplitType {
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Auto => "auto",
            Self::Agent => "agent",
            Self::Manual => "manual",
        }
    }
}

impl std::fmt::Display for SplitType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.as_str())
    }
}

/// Request to split members out of a canonical entity.
pub struct SplitRequest {
    pub tenant_id: Uuid,
    /// The canonical entity to split from.
    pub canonical_id: Uuid,
    /// External entity IDs (identity_link.external_entity_id) to eject.
    pub member_ids: Vec<Uuid>,
    pub split_type: SplitType,
    /// Arbitrary metadata included in the entity_events payload.
    pub metadata: serde_json::Value,
}

/// Options controlling split behavior.
pub struct SplitOptions {
    /// Recompute canonical_data via last-non-null survivorship on all affected entities.
    pub recompute_canonical: bool,
}

impl Default for SplitOptions {
    fn default() -> Self {
        Self {
            recompute_canonical: true,
        }
    }
}

/// A new entity created by the split.
#[derive(Debug, Serialize)]
pub struct NewEntity {
    pub canonical_id: Uuid,
    pub member_id: Uuid,
}

/// Result of a successful split.
#[derive(Debug, Serialize)]
pub struct SplitResult {
    pub original_id: Uuid,
    /// Members remaining on the original entity after split.
    pub remaining_members: i64,
    /// New entities created (one per ejected member).
    pub new_entities: Vec<NewEntity>,
}

/// Execute a split of members out of a canonical entity within the caller's
/// transaction.
///
/// The caller owns the transaction boundary. This function takes a
/// `&mut PgConnection` and performs all split steps atomically.
///
/// Steps:
/// 1. Verify member_ids is not empty
/// 2. Verify each member belongs to the source canonical entity
/// 3. For each ejected member: create new canonical_entity, move identity_link
/// 4. Emit entity.split event on original entity
/// 5. Recompute canonical_data on original entity
/// 6. Recompute canonical_data on each new entity
pub async fn execute_split(
    conn: &mut PgConnection,
    req: &SplitRequest,
    opts: &SplitOptions,
) -> crate::error::Result<SplitResult> {
    // 1. Verify member_ids is not empty
    if req.member_ids.is_empty() {
        return Err(crate::error::CannonError::BadRequest(
            "At least one member ID is required for split".into(),
        ));
    }

    // 2. Verify each member belongs to the source canonical entity
    for member_id in &req.member_ids {
        let belongs: 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\"",
            member_id,
            req.canonical_id,
            req.tenant_id,
        )
        .fetch_one(&mut *conn)
        .await?;

        if !belongs {
            return Err(crate::error::CannonError::BadRequest(format!(
                "Member {} does not belong to entity {}",
                member_id, req.canonical_id
            )));
        }
    }

    // 3. For each ejected member: create new canonical_entity, move identity_link
    let mut new_entities = Vec::with_capacity(req.member_ids.len());

    for member_id in &req.member_ids {
        let new_canonical_id = Uuid::new_v4();

        // Load ejected member's data for the new canonical record
        let member_data: serde_json::Value = sqlx::query_scalar!(
            "SELECT raw_data FROM external_entities WHERE id = $1 AND tenant_id = $2",
            member_id,
            req.tenant_id,
        )
        .fetch_optional(&mut *conn)
        .await?
        .unwrap_or(serde_json::json!({}));

        // Load entity_type from the original canonical
        let entity_type: String = sqlx::query_scalar!(
            "SELECT entity_type FROM canonical_entities WHERE id = $1 AND tenant_id = $2",
            req.canonical_id,
            req.tenant_id,
        )
        .fetch_optional(&mut *conn)
        .await?
        .ok_or_else(|| crate::error::CannonError::NotFound(format!(
            "Canonical entity {} not found",
            req.canonical_id
        )))?;

        // Insert new canonical entity
        sqlx::query!(
            "INSERT INTO canonical_entities (id, tenant_id, entity_type, canonical_data, created_at, updated_at, version, state) \
             VALUES ($1, $2, $3, $4, NOW(), NOW(), 1, 'active')",
            new_canonical_id,
            req.tenant_id,
            entity_type,
            member_data,
        )
        .execute(&mut *conn)
        .await?;

        // Move identity_link from original to new canonical
        sqlx::query!(
            "UPDATE identity_links SET canonical_entity_id = $1 \
             WHERE external_entity_id = $2 AND tenant_id = $3",
            new_canonical_id,
            member_id,
            req.tenant_id,
        )
        .execute(&mut *conn)
        .await?;

        new_entities.push(NewEntity {
            canonical_id: new_canonical_id,
            member_id: *member_id,
        });
    }

    // 4. Emit entity.split event on original entity
    let new_entity_ids: Vec<String> = new_entities
        .iter()
        .map(|ne| ne.canonical_id.to_string())
        .collect();
    let ejected_member_ids: Vec<String> = req.member_ids.iter().map(|id| id.to_string()).collect();

    let mut event_payload = serde_json::json!({
        "split_type": req.split_type.as_str(),
        "ejected_members": ejected_member_ids,
        "new_entity_ids": new_entity_ids,
    });
    // Merge caller's metadata into the event payload
    if let Some(obj) = req.metadata.as_object() {
        for (k, v) in obj {
            event_payload[k] = v.clone();
        }
    }

    // Idempotency key: sorted member IDs for deterministic replay
    let mut sorted_members: Vec<&str> = ejected_member_ids.iter().map(|s| s.as_str()).collect();
    sorted_members.sort();
    let idem_key = format!("split:{}:{}", req.canonical_id, sorted_members.join(","));
    sqlx::query!(
        "INSERT INTO entity_events (tenant_id, entity_id, event_type, payload, idempotency_key) \
         VALUES ($1, $2, 'entity.split', $3, $4) \
         ON CONFLICT (tenant_id, idempotency_key) WHERE idempotency_key IS NOT NULL DO NOTHING",
        req.tenant_id,
        req.canonical_id,
        &event_payload,
        &idem_key,
    )
    .execute(&mut *conn)
    .await?;

    // 5. Bump version on original entity (recompute_canonical_data also bumps, but
    //    if recompute is disabled we still need the version increment for the split itself)
    if opts.recompute_canonical {
        recompute_canonical_data(&mut *conn, req.tenant_id, req.canonical_id).await?;
    } else {
        sqlx::query!(
            "UPDATE canonical_entities SET version = version + 1 WHERE id = $1 AND tenant_id = $2",
            req.canonical_id,
            req.tenant_id,
        )
        .execute(&mut *conn)
        .await?;
    }

    // 6. Recompute canonical_data on each new entity
    if opts.recompute_canonical {
        for ne in &new_entities {
            recompute_canonical_data(&mut *conn, req.tenant_id, ne.canonical_id).await?;
        }
    }

    // Count remaining members on original
    let remaining_members: i64 = sqlx::query_scalar!(
        "SELECT COUNT(*) as \"count!: i64\" FROM identity_links WHERE canonical_entity_id = $1 AND tenant_id = $2",
        req.canonical_id,
        req.tenant_id,
    )
    .fetch_one(&mut *conn)
    .await?;

    Ok(SplitResult {
        original_id: req.canonical_id,
        remaining_members,
        new_entities,
    })
}

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

    #[test]
    fn test_split_type_as_str() {
        assert_eq!(SplitType::Auto.as_str(), "auto");
        assert_eq!(SplitType::Agent.as_str(), "agent");
        assert_eq!(SplitType::Manual.as_str(), "manual");
    }

    #[test]
    fn test_split_type_display() {
        assert_eq!(format!("{}", SplitType::Auto), "auto");
        assert_eq!(format!("{}", SplitType::Agent), "agent");
        assert_eq!(format!("{}", SplitType::Manual), "manual");
    }

    #[test]
    fn test_split_options_default() {
        let opts = SplitOptions::default();
        assert!(opts.recompute_canonical);
    }

    #[test]
    fn test_split_type_serde_roundtrip() {
        let auto = SplitType::Auto;
        let json = serde_json::to_string(&auto).unwrap();
        assert_eq!(json, r#""auto""#);
        let back: SplitType = serde_json::from_str(&json).unwrap();
        assert_eq!(back.as_str(), "auto");

        let manual = SplitType::Manual;
        let json = serde_json::to_string(&manual).unwrap();
        assert_eq!(json, r#""manual""#);
        let back: SplitType = serde_json::from_str(&json).unwrap();
        assert_eq!(back.as_str(), "manual");

        let agent = SplitType::Agent;
        let json = serde_json::to_string(&agent).unwrap();
        assert_eq!(json, r#""agent""#);
        let back: SplitType = serde_json::from_str(&json).unwrap();
        assert_eq!(back.as_str(), "agent");
    }

    #[test]
    fn test_split_result_serialization() {
        let result = SplitResult {
            original_id: Uuid::nil(),
            remaining_members: 3,
            new_entities: vec![
                NewEntity {
                    canonical_id: Uuid::nil(),
                    member_id: Uuid::nil(),
                },
            ],
        };
        let json = serde_json::to_value(&result).unwrap();
        assert_eq!(json["remaining_members"], 3);
        assert!(json["new_entities"].is_array());
        assert_eq!(json["new_entities"].as_array().unwrap().len(), 1);
        assert!(json["new_entities"][0].get("canonical_id").is_some());
        assert!(json["new_entities"][0].get("member_id").is_some());
    }

    #[test]
    fn test_new_entity_serialization() {
        let ne = NewEntity {
            canonical_id: Uuid::nil(),
            member_id: Uuid::nil(),
        };
        let json = serde_json::to_value(&ne).unwrap();
        assert!(json.get("canonical_id").is_some());
        assert!(json.get("member_id").is_some());
    }
}
