//! Follow/unfollow remote actors
//!
//! Implements sending Follow and Undo(Follow) activities to remote actors,
//! and handling Accept/Reject responses.

use sea_orm::{
    ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, ModelTrait, QueryFilter, Set,
};
use tracing::{info, instrument};
use uuid::Uuid;

use crate::activitypub::ApUrls;
use crate::entities::prelude::*;
use crate::entities::{ap_following, ap_remote_actors};

/// Send a Follow activity to a remote actor.
///
/// Creates a Follow activity, stores it in the database with state "pending",
/// and enqueues it for delivery to the remote actor's inbox.
#[instrument(skip(db, local_actor, remote_actor))]
pub async fn send_follow(
    db: &DatabaseConnection,
    public_url: &str,
    local_actor: &crate::entities::ap_actors::Model,
    remote_actor: &crate::entities::ap_remote_actors::Model,
) -> Result<(), String> {
    let urls = ApUrls::new(public_url);
    let user = match local_actor.user_id {
        Some(user_id) => Users::find_by_id(user_id)
            .one(db)
            .await
            .map_err(|e| format!("failed to fetch user: {}", e))?
            .ok_or("user not found")?,
        None => return Err("actor has no user_id".to_string()),
    };

    let local_actor_url = urls.actor(&user.name);
    let activity_uuid = Uuid::new_v4();
    let activity_url = urls.activity(&user.name, activity_uuid);

    // Build the Follow activity JSON
    let follow_activity = serde_json::json!({
        "@context": "https://www.w3.org/ns/activitystreams",
        "id": activity_url,
        "type": "Follow",
        "actor": local_actor_url,
        "object": remote_actor.actor_url
    });

    let activity_json = serde_json::to_string(&follow_activity)
        .map_err(|e| format!("failed to serialize Follow: {}", e))?;

    // Insert into ap_following with state "pending"
    let following_model = ap_following::ActiveModel {
        local_actor_id: Set(local_actor.id),
        remote_actor_id: Set(remote_actor.id),
        follow_activity_id: Set(activity_url.clone()),
        state: Set("pending".to_string()),
        ..Default::default()
    };

    following_model
        .insert(db)
        .await
        .map_err(|e| format!("failed to insert following: {}", e))?;

    // Enqueue delivery to remote inbox
    crate::activitypub::delivery::enqueue_delivery(
        db,
        local_actor.id,
        activity_uuid,
        activity_json,
        remote_actor.actor_url.clone(),
        remote_actor.inbox_url.clone(),
    )
    .await
    .map_err(|e| format!("failed to enqueue delivery: {}", e))?;

    info!(
        remote_actor_url = %remote_actor.actor_url,
        activity_url = %activity_url,
        "sent Follow activity"
    );

    Ok(())
}

/// Send an Undo(Follow) activity to a remote actor.
///
/// Creates an Undo activity wrapping the original Follow, enqueues it for
/// delivery, and removes the following relationship from the database.
#[instrument(skip(db, local_actor, remote_actor))]
pub async fn send_unfollow(
    db: &DatabaseConnection,
    public_url: &str,
    local_actor: &crate::entities::ap_actors::Model,
    remote_actor: &crate::entities::ap_remote_actors::Model,
) -> Result<(), String> {
    let urls = ApUrls::new(public_url);
    let user = match local_actor.user_id {
        Some(user_id) => Users::find_by_id(user_id)
            .one(db)
            .await
            .map_err(|e| format!("failed to fetch user: {}", e))?
            .ok_or("user not found")?,
        None => return Err("actor has no user_id".to_string()),
    };

    let local_actor_url = urls.actor(&user.name);
    let activity_uuid = Uuid::new_v4();
    let activity_url = urls.activity(&user.name, activity_uuid);

    // Find the following relationship
    let following = ApFollowing::find()
        .filter(ap_following::Column::LocalActorId.eq(local_actor.id))
        .filter(ap_following::Column::RemoteActorId.eq(remote_actor.id))
        .one(db)
        .await
        .map_err(|e| format!("failed to find following: {}", e))?
        .ok_or("not following this actor")?;

    // Build the Undo activity
    let undo_activity = serde_json::json!({
        "@context": "https://www.w3.org/ns/activitystreams",
        "id": activity_url,
        "type": "Undo",
        "actor": local_actor_url,
        "object": {
            "id": following.follow_activity_id,
            "type": "Follow",
            "actor": local_actor_url,
            "object": remote_actor.actor_url
        }
    });

    let activity_json = serde_json::to_string(&undo_activity)
        .map_err(|e| format!("failed to serialize Undo: {}", e))?;

    // Enqueue delivery to remote inbox
    crate::activitypub::delivery::enqueue_delivery(
        db,
        local_actor.id,
        activity_uuid,
        activity_json,
        remote_actor.actor_url.clone(),
        remote_actor.inbox_url.clone(),
    )
    .await
    .map_err(|e| format!("failed to enqueue delivery: {}", e))?;

    // Delete the following relationship
    following
        .delete(db)
        .await
        .map_err(|e| format!("failed to delete following: {}", e))?;

    info!(
        remote_actor_url = %remote_actor.actor_url,
        "sent Undo(Follow) activity"
    );

    Ok(())
}

/// Handle an Accept activity for a Follow.
///
/// Updates the following relationship state from "pending" to "accepted".
#[instrument(skip(db, accept))]
pub async fn handle_accept(
    db: &DatabaseConnection,
    verified_actor_url: &str,
    accept: &crate::activitypub::types::AcceptRejectActivity,
) -> Result<(), String> {
    // Extract the Follow activity ID from the object
    let follow_activity_id = match &accept.object {
        crate::activitypub::types::ObjectRef::Url(url) => url.clone(),
        crate::activitypub::types::ObjectRef::Embedded(obj) => {
            // If it's embedded, extract the id
            if obj.kind != "Follow" {
                return Err(format!("Accept for non-Follow activity: {}", obj.kind));
            }
            obj.id.clone()
        }
    };

    // Find the remote actor
    let remote_actor = ApRemoteActors::find()
        .filter(ap_remote_actors::Column::ActorUrl.eq(verified_actor_url))
        .one(db)
        .await
        .map_err(|e| format!("failed to find remote actor: {}", e))?
        .ok_or("remote actor not found")?;

    // Find the following relationship
    let following = ApFollowing::find()
        .filter(ap_following::Column::FollowActivityId.eq(&follow_activity_id))
        .filter(ap_following::Column::RemoteActorId.eq(remote_actor.id))
        .one(db)
        .await
        .map_err(|e| format!("failed to find following: {}", e))?
        .ok_or("following relationship not found")?;

    // Update state to "accepted"
    let mut active_model: ap_following::ActiveModel = following.into();
    active_model.state = Set("accepted".to_string());
    active_model
        .update(db)
        .await
        .map_err(|e| format!("failed to update following: {}", e))?;

    info!(
        remote_actor_url = %verified_actor_url,
        follow_activity_id = %follow_activity_id,
        "accepted Follow"
    );

    Ok(())
}

/// Handle a Reject activity for a Follow.
///
/// Updates the following relationship state from "pending" to "rejected".
#[instrument(skip(db, reject))]
pub async fn handle_reject(
    db: &DatabaseConnection,
    verified_actor_url: &str,
    reject: &crate::activitypub::types::AcceptRejectActivity,
) -> Result<(), String> {
    // Extract the Follow activity ID from the object
    let follow_activity_id = match &reject.object {
        crate::activitypub::types::ObjectRef::Url(url) => url.clone(),
        crate::activitypub::types::ObjectRef::Embedded(obj) => {
            // If it's embedded, extract the id
            if obj.kind != "Follow" {
                return Err(format!("Reject for non-Follow activity: {}", obj.kind));
            }
            obj.id.clone()
        }
    };

    // Find the remote actor
    let remote_actor = ApRemoteActors::find()
        .filter(ap_remote_actors::Column::ActorUrl.eq(verified_actor_url))
        .one(db)
        .await
        .map_err(|e| format!("failed to find remote actor: {}", e))?
        .ok_or("remote actor not found")?;

    // Find the following relationship
    let following = ApFollowing::find()
        .filter(ap_following::Column::FollowActivityId.eq(&follow_activity_id))
        .filter(ap_following::Column::RemoteActorId.eq(remote_actor.id))
        .one(db)
        .await
        .map_err(|e| format!("failed to find following: {}", e))?
        .ok_or("following relationship not found")?;

    // Update state to "rejected"
    let mut active_model: ap_following::ActiveModel = following.into();
    active_model.state = Set("rejected".to_string());
    active_model
        .update(db)
        .await
        .map_err(|e| format!("failed to update following: {}", e))?;

    info!(
        remote_actor_url = %verified_actor_url,
        follow_activity_id = %follow_activity_id,
        "rejected Follow"
    );

    Ok(())
}
