//! # Benzinga Historical Data Provider
//!
//! This module provides access to Benzinga's historical news and events data
//! through their REST API.

use chrono::{DateTime, Utc};
use reqwest::Client;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

use crate::error::{DataError, Result};
use crate::providers::common::{NewsEvent, NewsEventType};
use ::common::Symbol;

/// Benzinga channel information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenzingaChannel {
    /// Channel ID
    pub id: u32,
    /// Channel name
    pub name: String,
}

/// Benzinga tag information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenzingaTag {
    /// Tag ID
    pub id: u32,
    /// Tag name
    pub name: String,
}

/// Benzinga news article
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenzingaNewsArticle {
    /// Article ID
    pub id: u32,
    /// Article title
    pub title: String,
    /// Article body text
    pub body: String,
    /// Article author name
    pub author: Option<String>,
    /// Creation timestamp
    pub created: DateTime<Utc>,
    /// Last update timestamp
    pub updated: DateTime<Utc>,
    /// Article URL
    pub url: String,
    /// Article image URL
    pub image: Option<String>,
    /// Associated ticker symbols
    pub symbols: Vec<String>,
    /// Content channels
    pub channels: Vec<BenzingaChannel>,
    /// Content tags
    pub tags: Vec<BenzingaTag>,
    /// Sentiment score
    pub sentiment: Option<f64>,
}

/// Benzinga rating information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenzingaRating {
    /// Rating ID
    pub id: u32,
    /// Ticker symbol
    pub ticker: String,
    /// Company name
    pub name: String,
    /// Analyst name
    pub analyst: String,
    /// Analyst firm name
    pub firm: String,
    /// Rating action (upgrade/downgrade/initiated/reiterated)
    pub action: String,
    /// Current rating
    pub current_rating: String,
    /// Previous rating if applicable
    pub previous_rating: Option<String>,
    /// Current price target
    pub price_target: Option<Decimal>,
    /// Previous price target if applicable
    pub previous_price_target: Option<Decimal>,
    /// Analyst comment
    pub comment: Option<String>,
    /// Rating date
    pub rating_date: DateTime<Utc>,
    /// Event timestamp
    pub timestamp: DateTime<Utc>,
    /// Importance score (0-100)
    pub importance: Option<u32>,
}

/// Benzinga earnings information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenzingaEarnings {
    /// Earnings event ID
    pub id: u32,
    /// Ticker symbol
    pub ticker: String,
    /// Company name
    pub name: String,
    /// Earnings date
    pub date: DateTime<Utc>,
    /// Reporting period (e.g., Q1, Q2)
    pub period: String,
    /// Reporting period year
    pub period_year: u32,
    /// Estimated EPS
    pub eps_est: Option<f64>,
    /// Actual EPS
    pub eps: Option<f64>,
    /// EPS surprise (actual - estimated)
    pub eps_surprise: Option<f64>,
    /// Estimated revenue
    pub revenue_est: Option<f64>,
    /// Actual revenue
    pub revenue: Option<f64>,
    /// Revenue surprise (actual - estimated)
    pub revenue_surprise: Option<f64>,
    /// Time of day (BMO/AMC/DMT)
    pub time: Option<String>,
    /// Importance score (0-100)
    pub importance: Option<u32>,
}

/// Benzinga economic event information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenzingaEconomicEvent {
    /// Economic event ID
    pub id: u32,
    /// Event name
    pub name: String,
    /// Event description
    pub description: Option<String>,
    /// Event date and time
    pub date: DateTime<Utc>,
    /// Country code (e.g., US, GB)
    pub country: String,
    /// Event category
    pub category: String,
    /// Importance level (low/medium/high)
    pub importance: String,
    /// Actual reported value
    pub actual: Option<String>,
    /// Consensus forecast
    pub consensus: Option<String>,
    /// Previous value
    pub previous: Option<String>,
    /// Revised previous value
    pub previous_revised: Option<String>,
}

/// Configuration for Benzinga Historical Provider
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenzingaConfig {
    /// Benzinga Pro API key
    pub api_key: String,
    /// API endpoint base URL
    pub endpoint: String,
    /// News channels to monitor
    pub channels: Vec<String>,
    /// Symbols to get news for
    pub symbols: Vec<String>,
    /// Request timeout in seconds
    pub timeout_seconds: u64,
    /// Rate limit (requests per second)
    pub rate_limit: u32,
}

impl Default for BenzingaConfig {
    fn default() -> Self {
        Self {
            api_key: std::env::var("BENZINGA_API_KEY").unwrap_or_else(|_| String::new()),
            endpoint: "https://api.benzinga.com/api/v2".to_string(),
            channels: vec!["news".to_string(), "ratings".to_string()],
            symbols: vec![],
            timeout_seconds: 30,
            rate_limit: 5,
        }
    }
}

// Note: NewsEvent and NewsEventType are imported from common.rs module

/// Benzinga Historical Data Provider
pub struct BenzingaHistoricalProvider {
    config: BenzingaConfig,
    client: Client,
}

impl BenzingaHistoricalProvider {
    /// Create a new Benzinga historical provider
    pub fn new(config: BenzingaConfig) -> Result<Self> {
        if config.api_key.is_empty() {
            return Err(DataError::Configuration {
                field: "api_key".to_string(),
                message: "Benzinga API key is required".to_string(),
            });
        }

        let client = Client::builder()
            .timeout(std::time::Duration::from_secs(config.timeout_seconds))
            .build()
            .map_err(|e| DataError::Network {
                message: format!("Failed to create HTTP client: {}", e),
            })?;

        Ok(Self { config, client })
    }

    /// Get all news events for the specified symbols and time range
    pub async fn get_all_events(
        &self,
        _symbols: Option<&[&str]>,
        _start: DateTime<Utc>,
        _end: DateTime<Utc>,
    ) -> Result<Vec<NewsEvent>> {
        let mut events = Vec::new();

        // Get news events
        events.extend(self.get_news_events(_symbols, _start, _end).await?);

        // Get earnings events
        events.extend(self.get_earnings_events(_symbols, _start, _end).await?);

        // Get rating events
        events.extend(self.get_rating_events(_symbols, _start, _end).await?);

        // Sort by timestamp
        events.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));

        Ok(events)
    }

    /// Get news events
    pub async fn get_news_events(
        &self,
        symbols: Option<&[&str]>,
        start: DateTime<Utc>,
        end: DateTime<Utc>,
    ) -> Result<Vec<NewsEvent>> {
        let start_date = start.format("%Y-%m-%d").to_string();
        let end_date = end.format("%Y-%m-%d").to_string();

        let mut query_params = vec![
            ("token", self.config.api_key.as_str()),
            ("dateFrom", start_date.as_str()),
            ("dateTo", end_date.as_str()),
        ];

        let symbols_str = symbols.as_ref().map(|s| s.join(","));
        if let Some(ref symbols_str) = symbols_str {
            query_params.push(("tickers", symbols_str.as_str()));
        }

        let url = format!("{}/news", self.config.endpoint);
        let response = self
            .client
            .get(&url)
            .query(&query_params)
            .send()
            .await
            .map_err(|e| DataError::Network {
                message: format!("Failed to fetch news: {}", e),
            })?;

        if !response.status().is_success() {
            return Err(DataError::Network {
                message: format!("Benzinga API error: {}", response.status()),
            });
        }

        // For now, return empty vec - actual implementation would parse response
        Ok(Vec::new())
    }

    /// Get earnings events
    pub async fn get_earnings_events(
        &self,
        _symbols: Option<&[&str]>,
        _start: DateTime<Utc>,
        _end: DateTime<Utc>,
    ) -> Result<Vec<NewsEvent>> {
        // Placeholder implementation
        Ok(Vec::new())
    }

    /// Get rating events
    pub async fn get_rating_events(
        &self,
        _symbols: Option<&[&str]>,
        _start: DateTime<Utc>,
        _end: DateTime<Utc>,
    ) -> Result<Vec<NewsEvent>> {
        // Placeholder implementation
        Ok(Vec::new())
    }

    /// Convert Benzinga news article to NewsEvent
    #[allow(deprecated)] // Needed for backward compatibility with deprecated fields
    pub fn convert_news_article(&self, article: BenzingaNewsArticle) -> NewsEvent {
        let mut metadata = HashMap::new();
        metadata.insert("article_id".to_string(), article.id.to_string());
        if let Some(author) = &article.author {
            metadata.insert("author".to_string(), author.clone());
        }
        metadata.insert("url".to_string(), article.url.clone());
        if let Some(image) = &article.image {
            metadata.insert("image_url".to_string(), image.clone());
        }

        // Calculate importance based on tags
        let importance = if article
            .tags
            .iter()
            .any(|tag| tag.name.to_lowercase().contains("breaking"))
        {
            0.8
        } else {
            0.5
        };

        NewsEvent {
            symbol: None,
            symbols: article.symbols.into_iter().map(|s| s.into()).collect(),
            story_id: format!("benzinga_news_{}", article.id),
            headline: article.title,
            content: article.body,
            summary: "".to_string(),
            category: "News".to_string(),
            tags: article.tags.into_iter().map(|tag| tag.name).collect(),
            impact_score: None,
            importance,
            author: article.author.unwrap_or_default(),
            timestamp: article.created,
            published_at: article.updated,
            source: "Benzinga News".to_string(),
            url: article.url,
            sentiment_score: article.sentiment,
            sentiment: article.sentiment, // Deprecated: kept for compatibility
            event_type: NewsEventType::News,
        }
    }

    /// Convert Benzinga earnings to NewsEvent
    #[allow(deprecated)] // Needed for backward compatibility with deprecated fields
    pub fn convert_earnings_event(&self, earnings: BenzingaEarnings) -> NewsEvent {
        let mut metadata = HashMap::new();
        metadata.insert("earnings_id".to_string(), earnings.id.to_string());
        metadata.insert("period".to_string(), earnings.period.clone());
        metadata.insert("period_year".to_string(), earnings.period_year.to_string());
        if let Some(time) = &earnings.time {
            metadata.insert("earnings_time".to_string(), time.clone());
        }
        if let Some(eps_est) = earnings.eps_est {
            metadata.insert("eps_estimate".to_string(), eps_est.to_string());
        }
        if let Some(eps) = earnings.eps {
            metadata.insert("eps_actual".to_string(), eps.to_string());
        }

        let importance = earnings.importance.map(|i| i as f64 / 5.0).unwrap_or(0.6);

        NewsEvent {
            symbol: Some(Symbol::from(earnings.ticker.clone())),
            symbols: vec![Symbol::from(earnings.ticker.clone())],
            story_id: format!("benzinga_earnings_{}", earnings.id),
            headline: format!("Earnings: {}", earnings.name),
            content: format!(
                "{} ({}) {} {}",
                earnings.name, earnings.ticker, earnings.period, earnings.period_year
            ),
            summary: "".to_string(),
            category: "Earnings".to_string(),
            tags: vec!["Earnings".to_string()],
            impact_score: None,
            importance,
            author: "".to_string(),
            timestamp: earnings.date,
            published_at: earnings.date,
            source: "Benzinga Earnings".to_string(),
            url: "".to_string(),
            sentiment_score: None,
            sentiment: None,
            event_type: NewsEventType::Earnings,
        }
    }

    /// Convert analyst rating to NewsEvent
    #[allow(deprecated)] // Needed for backward compatibility with deprecated fields
    pub fn convert_rating_event(&self, rating: BenzingaRating) -> NewsEvent {
        let mut metadata = HashMap::new();
        metadata.insert("rating_id".to_string(), rating.id.to_string());
        metadata.insert("analyst".to_string(), rating.analyst.clone());
        metadata.insert("action".to_string(), rating.action.clone());
        metadata.insert("rating".to_string(), rating.current_rating.clone());
        if let Some(price_target) = rating.price_target {
            metadata.insert("price_target".to_string(), price_target.to_string());
        }

        let importance = rating.importance.map(|i| i as f64 / 5.0).unwrap_or(0.6);

        // Calculate sentiment based on action
        let sentiment = match rating.action.as_str() {
            "Upgrades" => Some(0.7),
            "Downgrades" => Some(-0.7),
            _ => None,
        };

        NewsEvent {
            symbol: Some(Symbol::from(rating.ticker.clone())),
            symbols: vec![Symbol::from(rating.ticker.clone())],
            story_id: format!("benzinga_rating_{}", rating.id),
            headline: format!("Rating: {} - {}", rating.name, rating.action),
            content: format!(
                "{} {} {} from {}",
                rating.analyst, rating.action, rating.name, rating.firm
            ),
            summary: "".to_string(),
            category: "Analyst Rating".to_string(),
            tags: vec!["Analyst Rating".to_string(), rating.action.clone()],
            impact_score: None,
            importance,
            author: rating.analyst.clone(),
            timestamp: rating.timestamp,
            published_at: rating.rating_date,
            source: "Benzinga Ratings".to_string(),
            url: "".to_string(),
            sentiment_score: sentiment,
            sentiment,
            event_type: NewsEventType::Rating,
        }
    }

    /// Convert economic calendar event to NewsEvent
    #[allow(deprecated)] // Needed for backward compatibility with deprecated fields
    pub fn convert_economic_event(&self, economic: BenzingaEconomicEvent) -> NewsEvent {
        let mut metadata = HashMap::new();
        metadata.insert("economic_id".to_string(), economic.id.to_string());
        metadata.insert("country".to_string(), economic.country.clone());
        metadata.insert("category".to_string(), economic.category.clone());
        metadata.insert("importance".to_string(), economic.importance.clone());
        if let Some(description) = &economic.description {
            metadata.insert("description".to_string(), description.clone());
        }
        if let Some(actual) = &economic.actual {
            metadata.insert("actual".to_string(), actual.clone());
        }

        let importance = match economic.importance.as_str() {
            "High" => 0.8,
            "Medium" => 0.5,
            "Low" => 0.2,
            _ => 0.4,
        };

        NewsEvent {
            symbol: None,
            symbols: vec![], // Economic events don't have specific symbols
            story_id: format!("benzinga_economic_{}", economic.id),
            headline: format!("Economic: {} ({})", economic.name, economic.country),
            content: format!(
                "{} - {} economic indicator for {}",
                economic.name, economic.importance, economic.country
            ),
            summary: "".to_string(),
            category: economic.category.clone(),
            tags: vec![economic.category.clone(), economic.importance.clone()],
            impact_score: None,
            importance,
            author: "".to_string(),
            timestamp: economic.date,
            published_at: economic.date,
            source: "Benzinga Economic".to_string(),
            url: "".to_string(),
            sentiment_score: None,
            sentiment: None,
            event_type: NewsEventType::Economic,
        }
    }

    /// Enforce rate limiting
    pub async fn enforce_rate_limit(&self) {
        let delay_ms = 1000 / self.config.rate_limit as u64;
        tokio::time::sleep(std::time::Duration::from_millis(delay_ms)).await;
    }
}

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

    #[test]
    fn test_config_default() {
        let config = BenzingaConfig::default();
        assert_eq!(config.endpoint, "https://api.benzinga.com/api/v2");
        assert_eq!(config.timeout_seconds, 30);
        assert_eq!(config.rate_limit, 5);
    }

    #[test]
    fn test_config_with_api_key() {
        let config = BenzingaConfig {
            api_key: "test-key".to_string(),
            ..Default::default()
        };

        let result = BenzingaHistoricalProvider::new(config);
        assert!(result.is_ok());
    }

    #[test]
    fn test_config_without_api_key() {
        let config = BenzingaConfig {
            api_key: "".to_string(),
            ..Default::default()
        };

        let result = BenzingaHistoricalProvider::new(config);
        assert!(result.is_err());
    }

    #[test]
    fn test_news_event_type_serialization() {
        let event_types = vec![
            NewsEventType::News,
            NewsEventType::Earnings,
            NewsEventType::Rating,
            NewsEventType::Economic,
            NewsEventType::CorporateAction,
        ];

        for event_type in event_types {
            let json = serde_json::to_string(&event_type).unwrap();
            let deserialized: std::result::Result<NewsEventType, _> = serde_json::from_str(&json);
            assert!(deserialized.is_ok());
            assert_eq!(deserialized.unwrap(), event_type);
        }
    }

    #[test]
    fn test_news_event_serialization() {
        let mut metadata = HashMap::new();
        metadata.insert("article_id".to_string(), "12345".to_string());
        metadata.insert("author".to_string(), "Test Author".to_string());

        let news_event = NewsEvent {
            story_id: "test_news_123".to_string(),
            timestamp: Utc::now(),
            published_at: Utc::now(),
            event_type: NewsEventType::News,
            symbol: Some(Symbol::new("AAPL".to_string())),
            symbols: vec![
                Symbol::new("AAPL".to_string()),
                Symbol::new("MSFT".to_string()),
            ],
            headline: "Tech Stocks Rise".to_string(),
            content: "Technology stocks showed strong performance today...".to_string(),
            summary: "Tech stocks perform well".to_string(),
            category: "Technology".to_string(),
            tags: vec!["Technology".to_string(), "Markets".to_string()],
            importance: 0.75,
            impact_score: Some(0.7),
            #[allow(deprecated)]
            sentiment: Some(0.6),
            sentiment_score: Some(0.6),
            author: metadata.get("author").cloned().unwrap_or_default(),
            source: "Test Source".to_string(),
            url: "https://test.com/news/123".to_string(),
        };

        let json = serde_json::to_string(&news_event).unwrap();
        let deserialized: std::result::Result<NewsEvent, _> = serde_json::from_str(&json);
        assert!(deserialized.is_ok());
    }
}
