use std::{collections::HashMap, sync::Arc, time::Duration};

use tracing::{error, instrument};

use crate::{
    channel::NotificationChannel,
    config::{ChannelConfig, ChannelsConfig},
    discord::DiscordChannel,
    error::{ChannelError, Result},
    message::Message,
    slack::SlackChannel,
    telegram::TelegramChannel,
};

/// Routes outbound notifications to named channels.
///
/// Each channel is addressed by the name given in the config (e.g.
/// `"team-slack"`, `"ops-discord"`). `send` delivers to one channel;
/// `broadcast` fans out to all registered channels in parallel.
pub struct ChannelRegistry {
    channels: HashMap<String, Arc<dyn NotificationChannel>>,
}

impl ChannelRegistry {
    /// Creates an empty registry. Use `register` to add channels individually
    /// (e.g. in tests with pre-built clients).
    pub fn new() -> Self {
        Self {
            channels: HashMap::new(),
        }
    }

    /// Builds all channels from `config` with a shared reqwest::Client.
    ///
    /// The client is configured with a 10 s timeout and a `vapora-channels`
    /// User-Agent. Fails if the TLS backend cannot be initialised.
    pub fn from_config(config: ChannelsConfig) -> Result<Self> {
        let client = reqwest::Client::builder()
            .timeout(Duration::from_secs(10))
            .user_agent(concat!("vapora-channels/", env!("CARGO_PKG_VERSION")))
            .build()
            .map_err(|e| ChannelError::HttpClientBuild(e.to_string()))?;

        // Resolve ${VAR} references in every channel's credential fields before
        // constructing any HTTP client — this is the single mandatory call site.
        let config = config.resolve_secrets()?;

        let mut registry = Self::new();
        for (name, ch_config) in config.channels {
            let channel: Arc<dyn NotificationChannel> = match ch_config {
                ChannelConfig::Slack(cfg) => {
                    Arc::new(SlackChannel::new(name.clone(), cfg, client.clone()))
                }
                ChannelConfig::Discord(cfg) => {
                    Arc::new(DiscordChannel::new(name.clone(), cfg, client.clone()))
                }
                ChannelConfig::Telegram(cfg) => {
                    Arc::new(TelegramChannel::new(name.clone(), cfg, client.clone()))
                }
            };
            registry.channels.insert(name, channel);
        }
        Ok(registry)
    }

    /// Builds all channels from a flat map, creating a shared
    /// `reqwest::Client`.
    ///
    /// Equivalent to wrapping the map in `ChannelsConfig` and calling
    /// `from_config`. Use this when you hold the channel entries directly
    /// (e.g. from `WorkflowsConfig.channels`).
    pub fn from_map(channels: std::collections::HashMap<String, ChannelConfig>) -> Result<Self> {
        Self::from_config(ChannelsConfig { channels })
    }

    /// Registers an already-constructed channel implementation.
    pub fn register(&mut self, channel: Arc<dyn NotificationChannel>) -> &mut Self {
        self.channels.insert(channel.name().to_string(), channel);
        self
    }

    /// Sends `msg` to a single channel identified by `name`.
    #[instrument(skip(self, msg), fields(channel = %name))]
    pub async fn send(&self, name: &str, msg: Message) -> Result<()> {
        let channel = self
            .channels
            .get(name)
            .ok_or_else(|| ChannelError::NotFound(name.to_string()))?;
        channel.send(&msg).await
    }

    /// Sends `msg` to every registered channel sequentially.
    ///
    /// Returns a `Vec` of `(channel_name, Result)` — failures do not abort
    /// delivery to remaining channels.
    pub async fn broadcast(&self, msg: Message) -> Vec<(String, Result<()>)> {
        let mut results = Vec::with_capacity(self.channels.len());
        for (name, channel) in &self.channels {
            let result = channel.send(&msg).await;
            if let Err(ref e) = result {
                error!(channel = %name, error = %e, "Broadcast delivery failed");
            }
            results.push((name.clone(), result));
        }
        results
    }

    /// Returns the names of all registered channels.
    pub fn channel_names(&self) -> Vec<&str> {
        self.channels.keys().map(String::as_str).collect()
    }

    pub fn is_empty(&self) -> bool {
        self.channels.is_empty()
    }
}

impl Default for ChannelRegistry {
    fn default() -> Self {
        Self::new()
    }
}
