use std::sync::Arc;

use async_trait::async_trait;
use vapora_channels::{
    config::{DiscordConfig, SlackConfig, TelegramConfig},
    discord::DiscordChannel,
    error::ChannelError,
    message::Message,
    registry::ChannelRegistry,
    slack::SlackChannel,
    telegram::TelegramChannel,
    NotificationChannel, Result,
};
use wiremock::{
    matchers::{method, path},
    Mock, MockServer, ResponseTemplate,
};

// ── Slack ─────────────────────────────────────────────────────────────────────

#[tokio::test]
async fn slack_send_returns_ok_on_200() {
    let server = MockServer::start().await;

    Mock::given(method("POST"))
        .and(path("/hooks/slack"))
        .respond_with(ResponseTemplate::new(200).set_body_string("ok"))
        .mount(&server)
        .await;

    let cfg = SlackConfig {
        webhook_url: format!("{}/hooks/slack", server.uri()),
        channel: None,
        username: None,
    };
    let channel = SlackChannel::new("slack", cfg, reqwest::Client::new());
    let msg = Message::success("Deploy complete", "v1.2.0 → production");

    channel.send(&msg).await.expect("should succeed on 200");
}

#[tokio::test]
async fn slack_send_returns_api_error_on_500() {
    let server = MockServer::start().await;

    Mock::given(method("POST"))
        .and(path("/hooks/slack"))
        .respond_with(ResponseTemplate::new(500).set_body_string("internal_error"))
        .mount(&server)
        .await;

    let cfg = SlackConfig {
        webhook_url: format!("{}/hooks/slack", server.uri()),
        channel: None,
        username: None,
    };
    let channel = SlackChannel::new("slack", cfg, reqwest::Client::new());
    let err = channel
        .send(&Message::info("Test", "Body"))
        .await
        .unwrap_err();

    assert!(
        matches!(
            err,
            ChannelError::ApiError {
                status: 500,
                ref body,
                ..
            } if body == "internal_error"
        ),
        "unexpected error variant: {err}"
    );
}

// ── Discord
// ───────────────────────────────────────────────────────────────────

#[tokio::test]
async fn discord_send_returns_ok_on_204() {
    let server = MockServer::start().await;

    Mock::given(method("POST"))
        .and(path("/webhooks/discord"))
        .respond_with(ResponseTemplate::new(204))
        .mount(&server)
        .await;

    let cfg = DiscordConfig {
        webhook_url: format!("{}/webhooks/discord", server.uri()),
        username: None,
        avatar_url: None,
    };
    let channel = DiscordChannel::new("discord", cfg, reqwest::Client::new());

    channel
        .send(&Message::warning("High latency", "p99 > 500 ms"))
        .await
        .expect("should succeed on 204");
}

#[tokio::test]
async fn discord_send_returns_api_error_on_400() {
    let server = MockServer::start().await;

    Mock::given(method("POST"))
        .and(path("/webhooks/discord"))
        .respond_with(ResponseTemplate::new(400).set_body_string("{\"code\":50006}"))
        .mount(&server)
        .await;

    let cfg = DiscordConfig {
        webhook_url: format!("{}/webhooks/discord", server.uri()),
        username: None,
        avatar_url: None,
    };
    let channel = DiscordChannel::new("discord", cfg, reqwest::Client::new());
    let err = channel
        .send(&Message::info("Test", "Body"))
        .await
        .unwrap_err();

    assert!(
        matches!(err, ChannelError::ApiError { status: 400, .. }),
        "unexpected error variant: {err}"
    );
}

// ── Telegram
// ──────────────────────────────────────────────────────────────────

#[tokio::test]
async fn telegram_send_returns_ok_on_200() {
    let server = MockServer::start().await;

    // Telegram returns {"ok": true, "result": {...}} with HTTP 200.
    Mock::given(method("POST"))
        .and(path("/botTEST_TOKEN/sendMessage"))
        .respond_with(
            ResponseTemplate::new(200).set_body_string(r#"{"ok":true,"result":{"message_id":1}}"#),
        )
        .mount(&server)
        .await;

    let cfg = TelegramConfig {
        bot_token: "TEST_TOKEN".to_string(),
        chat_id: "-100999".to_string(),
        api_base: Some(server.uri()),
    };
    let channel = TelegramChannel::new("telegram", cfg, reqwest::Client::new());

    channel
        .send(&Message::error("Service down", "Critical alert"))
        .await
        .expect("should succeed on 200");
}

#[tokio::test]
async fn telegram_send_returns_api_error_on_400() {
    let server = MockServer::start().await;

    Mock::given(method("POST"))
        .and(path("/botBAD_TOKEN/sendMessage"))
        .respond_with(
            ResponseTemplate::new(400)
                .set_body_string(r#"{"ok":false,"description":"Unauthorized"}"#),
        )
        .mount(&server)
        .await;

    let cfg = TelegramConfig {
        bot_token: "BAD_TOKEN".to_string(),
        chat_id: "-100".to_string(),
        api_base: Some(server.uri()),
    };
    let channel = TelegramChannel::new("telegram", cfg, reqwest::Client::new());
    let err = channel
        .send(&Message::info("Test", "Body"))
        .await
        .unwrap_err();

    assert!(
        matches!(err, ChannelError::ApiError { status: 400, .. }),
        "unexpected error variant: {err}"
    );
}

// ── Registry
// ──────────────────────────────────────────────────────────────────

struct AlwaysOkChannel {
    name: String,
}

#[async_trait]
impl NotificationChannel for AlwaysOkChannel {
    fn name(&self) -> &str {
        &self.name
    }
    async fn send(&self, _msg: &Message) -> Result<()> {
        Ok(())
    }
}

struct AlwaysFailChannel {
    name: String,
}

#[async_trait]
impl NotificationChannel for AlwaysFailChannel {
    fn name(&self) -> &str {
        &self.name
    }
    async fn send(&self, _msg: &Message) -> Result<()> {
        Err(ChannelError::ApiError {
            channel: self.name.clone(),
            status: 503,
            body: "unavailable".to_string(),
        })
    }
}

#[tokio::test]
async fn registry_send_routes_to_named_channel() {
    let mut registry = ChannelRegistry::new();
    registry.register(Arc::new(AlwaysOkChannel {
        name: "ok-channel".to_string(),
    }));

    registry
        .send("ok-channel", Message::info("Test", "Body"))
        .await
        .expect("should route to ok-channel and succeed");
}

#[tokio::test]
async fn registry_send_returns_not_found_for_unknown_channel() {
    let registry = ChannelRegistry::new();
    let err = registry
        .send("does-not-exist", Message::info("Test", "Body"))
        .await
        .unwrap_err();

    assert!(
        matches!(err, ChannelError::NotFound(ref n) if n == "does-not-exist"),
        "unexpected error: {err}"
    );
}

#[tokio::test]
async fn registry_broadcast_delivers_to_all_channels() {
    let mut registry = ChannelRegistry::new();
    registry.register(Arc::new(AlwaysOkChannel {
        name: "ch-a".to_string(),
    }));
    registry.register(Arc::new(AlwaysOkChannel {
        name: "ch-b".to_string(),
    }));

    let results = registry
        .broadcast(Message::success("All systems green", ""))
        .await;

    assert_eq!(results.len(), 2);
    assert!(results.iter().all(|(_, r)| r.is_ok()));
}

#[tokio::test]
async fn registry_broadcast_continues_after_partial_failure() {
    let mut registry = ChannelRegistry::new();
    registry.register(Arc::new(AlwaysOkChannel {
        name: "good".to_string(),
    }));
    registry.register(Arc::new(AlwaysFailChannel {
        name: "bad".to_string(),
    }));

    let results = registry.broadcast(Message::info("Test", "Body")).await;

    assert_eq!(results.len(), 2);
    let ok_count = results.iter().filter(|(_, r)| r.is_ok()).count();
    let err_count = results.iter().filter(|(_, r)| r.is_err()).count();
    assert_eq!(ok_count, 1);
    assert_eq!(err_count, 1);
}
