// SPDX-FileCopyrightText: 2026 Ololduck <ololduck@vit.am> and contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later

//! Instance actor endpoints.
//!
//! The instance actor is a singleton `Application` actor at `/actor` used for
//! admin-level federation activities (signing Blocks, receiving forwarded
//! Flags).

use std::sync::Arc;

use actix_web::{HttpRequest, HttpResponse, web};
use serde_json::json;
use tokio::sync::RwLock;
use tracing::error;

use crate::activitypub::{self, ApUrls};
use crate::{AppState, state_read};

/// GET /actor with ActivityPub Accept header
///
/// Serves the instance actor as an `Application` type. Returns 404 for browsers
/// (non-AP requests) to avoid confusion with a regular web page.
#[tracing::instrument(skip_all)]
pub async fn get_instance_actor(data: web::Data<Arc<RwLock<AppState>>>) -> HttpResponse {
    let state = state_read(&data).await;
    if !activitypub::enabled(&state) {
        return HttpResponse::NotFound().finish();
    }

    // Fetch or create the instance actor (keypair generated lazily).
    let actor = match activitypub::actor::get_or_create_instance(&state.db).await {
        Ok(actor) => actor,
        Err(err) => {
            error!(%err, "fetching or creating instance actor failed");
            return HttpResponse::InternalServerError().finish();
        }
    };

    let urls = ApUrls::new(state.public_url());
    let domain = urls
        .base()
        .strip_prefix("http://")
        .or_else(|| urls.base().strip_prefix("https://"))
        .unwrap_or(urls.base());

    // Build the Application actor document
    let actor_doc = json!({
        "@context": activitypub::types::CONTEXT,
        "id": urls.instance_actor(),
        "type": "Application",
        "preferredUsername": domain,
        "inbox": urls.instance_actor_inbox(),
        "outbox": urls.instance_actor_outbox(),
        "publicKey": {
            "id": urls.instance_actor_key_id(),
            "owner": urls.instance_actor(),
            "publicKeyPem": actor.public_key_pem,
        }
    });

    HttpResponse::Ok()
        .content_type("application/activity+json; charset=utf-8")
        .json(actor_doc)
}

/// POST /actor/inbox
///
/// Receives activities addressed to the instance actor. Processes Flag
/// activities for moderation reports and accepts other activities with 202.
#[tracing::instrument(skip_all)]
pub async fn post_instance_actor_inbox(
    data: web::Data<Arc<RwLock<AppState>>>,
    req: HttpRequest,
    body: web::Bytes,
) -> HttpResponse {
    let state = state_read(&data).await;
    if !activitypub::enabled(&state) {
        return HttpResponse::NotFound().finish();
    }

    // Fetch or create the instance actor (needed for signature verification)
    let actor = match activitypub::actor::get_or_create_instance(&state.db).await {
        Ok(actor) => actor,
        Err(err) => {
            error!(%err, "fetching or creating instance actor failed");
            return HttpResponse::InternalServerError().finish();
        }
    };

    activitypub::inbox::handle(
        &state.db,
        state.http_client(),
        state.public_url(),
        &actor,
        &req,
        &body,
    )
    .await
}
