//! Slack Bot API integration.
//!
//! Provides:
//! - [`SlackTransport`] — a [`ChatTransport`] that sends messages via the
//!   Slack Web API (`api.slack.com/api/chat.postMessage` / `chat.update`).
//! - [`webhook_receive`] — Poem handler for the Slack Events API webhook
//!   (POST incoming events including URL verification challenge).

/// Slack bot command handlers — parses and dispatches bot commands from Slack messages.
pub mod commands;
/// Slack message formatter — converts markdown to Slack mrkdwn syntax.
pub mod format;
/// Slack conversation history — loads prior chat history for context.
pub mod history;
/// SlackTransport — `ChatTransport` implementation for the Slack Bot API.
pub mod meta;
/// Slack request signature verification — validates HMAC-SHA256 signatures on incoming webhooks.
pub mod verify;

pub use commands::SlackWebhookContext;
pub use format::markdown_to_slack;
pub use history::load_slack_history;
pub use meta::SlackTransport;

use serde::Deserialize;

use crate::slog;
use poem::{Request, Response, handler, http::StatusCode};

// ── Slack Events API types ──────────────────────────────────────────────

/// Outer envelope for Slack Events API callbacks.
///
/// Slack sends three types of payloads:
/// - `url_verification`: challenge-response handshake during app setup
/// - `event_callback`: actual events (messages, reactions, etc.)
#[derive(Deserialize, Debug)]
pub struct SlackEventEnvelope {
    pub r#type: String,
    /// Present only for `url_verification` events.
    pub challenge: Option<String>,
    /// Present only for `event_callback` events.
    pub event: Option<SlackEvent>,
}

/// A single Slack event payload (message, reaction, etc.) nested inside an event callback.
#[derive(Deserialize, Debug)]
pub struct SlackEvent {
    pub r#type: Option<String>,
    /// Channel or DM where the message was sent.
    pub channel: Option<String>,
    /// User who sent the message.
    pub user: Option<String>,
    /// Message text.
    pub text: Option<String>,
    /// Bot ID — present if the message was sent by a bot.
    pub bot_id: Option<String>,
    /// Subtype (e.g. "bot_message", "message_changed") — absent for plain user messages.
    pub subtype: Option<String>,
}

// ── Webhook handlers ────────────────────────────────────────────────────

/// POST /webhook/slack — receive incoming events from Slack Events API.
///
/// Handles both `url_verification` (challenge-response handshake) and
/// `event_callback` (incoming messages) event types.
#[handler]
pub async fn webhook_receive(
    req: &Request,
    body: poem::Body,
    ctx: poem::web::Data<&std::sync::Arc<SlackWebhookContext>>,
) -> Response {
    use std::sync::Arc;

    let timestamp = req
        .header("X-Slack-Request-Timestamp")
        .unwrap_or("")
        .to_string();
    let signature = req.header("X-Slack-Signature").unwrap_or("").to_string();

    let bytes = match body.into_bytes().await {
        Ok(b) => b,
        Err(e) => {
            slog!("[slack] Failed to read webhook body: {e}");
            return Response::builder()
                .status(StatusCode::BAD_REQUEST)
                .body("Bad request");
        }
    };

    // Verify request signature.
    if !verify::verify_slack_signature(&ctx.signing_secret, &timestamp, &bytes, &signature) {
        slog!("[slack] Webhook signature verification failed");
        return Response::builder()
            .status(StatusCode::UNAUTHORIZED)
            .body("Invalid signature");
    }

    let envelope: SlackEventEnvelope = match serde_json::from_slice(&bytes) {
        Ok(e) => e,
        Err(e) => {
            slog!("[slack] Failed to parse webhook payload: {e}");
            return Response::builder().status(StatusCode::OK).body("ok");
        }
    };

    // Handle URL verification challenge.
    if envelope.r#type == "url_verification" {
        if let Some(challenge) = envelope.challenge {
            slog!("[slack] URL verification succeeded");
            return Response::builder()
                .status(StatusCode::OK)
                .content_type("text/plain")
                .body(challenge);
        }
        return Response::builder()
            .status(StatusCode::BAD_REQUEST)
            .body("Missing challenge");
    }

    // Handle event callbacks.
    if envelope.r#type == "event_callback"
        && let Some(event) = envelope.event
        && event.r#type.as_deref() == Some("message")
        && event.subtype.is_none()
        && event.bot_id.is_none()
        && let (Some(channel), Some(user), Some(text)) = (event.channel, event.user, event.text)
        && ctx.channel_ids.contains(&channel)
    {
        let ctx = Arc::clone(*ctx);
        tokio::spawn(async move {
            slog!("[slack] Message from {user} in {channel}: {text}");
            commands::handle_incoming_message(&ctx, &channel, &user, &text).await;
        });
    }

    Response::builder().status(StatusCode::OK).body("ok")
}

/// POST /webhook/slack/command — receive incoming Slack slash commands.
///
/// Slash commands arrive as `application/x-www-form-urlencoded` POST requests.
/// The response is JSON with `response_type: "ephemeral"` so only the invoking
/// user sees the reply.
#[handler]
pub async fn slash_command_receive(
    req: &Request,
    body: poem::Body,
    ctx: poem::web::Data<&std::sync::Arc<SlackWebhookContext>>,
) -> Response {
    let timestamp = req
        .header("X-Slack-Request-Timestamp")
        .unwrap_or("")
        .to_string();
    let signature = req.header("X-Slack-Signature").unwrap_or("").to_string();

    let bytes = match body.into_bytes().await {
        Ok(b) => b,
        Err(e) => {
            slog!("[slack] Failed to read slash command body: {e}");
            return Response::builder()
                .status(StatusCode::BAD_REQUEST)
                .body("Bad request");
        }
    };

    // Verify request signature.
    if !verify::verify_slack_signature(&ctx.signing_secret, &timestamp, &bytes, &signature) {
        slog!("[slack] Slash command signature verification failed");
        return Response::builder()
            .status(StatusCode::UNAUTHORIZED)
            .body("Invalid signature");
    }

    let payload: commands::SlackSlashCommandPayload = match serde_urlencoded::from_bytes(&bytes) {
        Ok(p) => p,
        Err(e) => {
            slog!("[slack] Failed to parse slash command payload: {e}");
            return Response::builder()
                .status(StatusCode::BAD_REQUEST)
                .body("Bad request");
        }
    };

    slog!(
        "[slack] Slash command from {}: {} {}",
        payload.user_id,
        payload.command,
        payload.text
    );

    let keyword = match commands::slash_command_to_bot_keyword(&payload.command) {
        Some(k) => k,
        None => {
            let resp = commands::SlashCommandResponse {
                response_type: "ephemeral",
                text: format!("Unknown command: {}", payload.command),
            };
            return Response::builder()
                .status(StatusCode::OK)
                .content_type("application/json")
                .body(serde_json::to_string(&resp).unwrap_or_default());
        }
    };

    // Build a synthetic message that the command registry can parse.
    // The format is "<bot_name> <keyword> <args>" so strip_bot_mention + dispatch works.
    let synthetic_message = if payload.text.is_empty() {
        format!("{} {keyword}", ctx.services.bot_name)
    } else {
        format!("{} {keyword} {}", ctx.services.bot_name, payload.text)
    };

    use crate::chat::commands::{CommandDispatch, try_handle_command};

    let dispatch = CommandDispatch {
        services: &ctx.services,
        project_root: &ctx.services.project_root,
        bot_user_id: &ctx.services.bot_user_id,
        room_id: &payload.channel_id,
    };

    let response_text = try_handle_command(&dispatch, &synthetic_message)
        .unwrap_or_else(|| format!("Command `{keyword}` did not produce a response."));
    let response_text = markdown_to_slack(&response_text);

    let resp = commands::SlashCommandResponse {
        response_type: "ephemeral",
        text: response_text,
    };

    Response::builder()
        .status(StatusCode::OK)
        .content_type("application/json")
        .body(serde_json::to_string(&resp).unwrap_or_default())
}

// ── Tests ───────────────────────────────────────────────────────────────

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

    #[test]
    fn parse_url_verification_event() {
        let json = r#"{
            "type": "url_verification",
            "challenge": "3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P"
        }"#;
        let envelope: SlackEventEnvelope = serde_json::from_str(json).unwrap();
        assert_eq!(envelope.r#type, "url_verification");
        assert_eq!(
            envelope.challenge.as_deref(),
            Some("3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P")
        );
    }

    #[test]
    fn parse_message_event() {
        let json = r#"{
            "type": "event_callback",
            "event": {
                "type": "message",
                "channel": "C01ABCDEF",
                "user": "U01GHIJKL",
                "text": "help"
            }
        }"#;
        let envelope: SlackEventEnvelope = serde_json::from_str(json).unwrap();
        assert_eq!(envelope.r#type, "event_callback");
        let event = envelope.event.unwrap();
        assert_eq!(event.r#type.as_deref(), Some("message"));
        assert_eq!(event.channel.as_deref(), Some("C01ABCDEF"));
        assert_eq!(event.user.as_deref(), Some("U01GHIJKL"));
        assert_eq!(event.text.as_deref(), Some("help"));
        assert!(event.bot_id.is_none());
        assert!(event.subtype.is_none());
    }

    #[test]
    fn parse_bot_message_has_bot_id() {
        let json = r#"{
            "type": "event_callback",
            "event": {
                "type": "message",
                "channel": "C01ABCDEF",
                "bot_id": "B01234",
                "text": "I am a bot"
            }
        }"#;
        let envelope: SlackEventEnvelope = serde_json::from_str(json).unwrap();
        let event = envelope.event.unwrap();
        assert!(event.bot_id.is_some());
    }

    #[test]
    fn parse_message_with_subtype() {
        let json = r#"{
            "type": "event_callback",
            "event": {
                "type": "message",
                "subtype": "message_changed",
                "channel": "C01ABCDEF"
            }
        }"#;
        let envelope: SlackEventEnvelope = serde_json::from_str(json).unwrap();
        let event = envelope.event.unwrap();
        assert_eq!(event.subtype.as_deref(), Some("message_changed"));
    }
}
