//! # Technical Indicators Module
//!
//! This module provides repository abstractions and implementations for storing,
//! retrieving, and managing technical indicator data. It supports various indicator
//! types including moving averages, oscillators, and volume-based indicators.
//!
//! ## Features
//!
//! - Storage and retrieval of multiple indicator types
//! - Batch operations for efficient data processing
//! - Historical data queries with time range filtering
//! - Statistical analysis of indicator values
//! - Data cleanup and maintenance operations
//!
//! ## Usage
//!
//! ```rust
//! use market_data::indicators::{IndicatorRepository, PostgresIndicatorRepository};
//! use market_data::models::{TechnicalIndicator, IndicatorType};
//! use sqlx::PgPool;
//!
//! # async fn example(pool: PgPool) -> Result<(), Box<dyn std::error::Error>> {
//! let repo = PostgresIndicatorRepository::new(pool);
//!
//! // Get latest RSI for a symbol
//! let rsi = repo.get_latest_indicator("AAPL", IndicatorType::Rsi).await?;
//!
//! // Get indicator history
//! let start = Utc::now() - Duration::days(30);
//! let end = Utc::now();
//! let history = repo.get_indicator_history("AAPL", IndicatorType::Rsi, start, end).await?;
//! # Ok(())
//! # }
//! ```

use async_trait::async_trait;
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use sqlx::{PgPool, Row};
use std::collections::HashMap;

use crate::{
    error::{MarketDataError, MarketDataResult},
    models::{IndicatorType, TechnicalIndicator},
};

/// Repository trait for technical indicator data operations
///
/// This trait defines the interface for storing, retrieving, and managing
/// technical indicator data. Implementations should provide efficient
/// data access patterns optimized for time-series queries.
///
/// The trait supports:
/// - Individual and batch storage operations
///
/// - Historical data retrieval with time filtering
/// - Multi-symbol and multi-indicator queries
///
/// - Statistical analysis and data maintenance
#[async_trait]
pub trait IndicatorRepository {
    /// Store a single technical indicator
    ///
    /// Stores a technical indicator value in the repository. If an indicator
    /// with the same symbol, type, and timestamp already exists, it will be updated.
    ///
    /// # Arguments
    ///
    /// * `indicator` - The technical indicator to store
    ///
    /// # Returns
    ///
    /// `Ok(())` on success, or a `MarketDataError` if the operation fails
    ///
    /// # Errors
    ///
    /// - `MarketDataError::InvalidSymbol` if the symbol is invalid
    /// - `MarketDataError::Database` if the database operation fails
    async fn store_indicator(&self, indicator: &TechnicalIndicator) -> MarketDataResult<()>;

    /// Store multiple technical indicators in a batch
    ///
    /// Efficiently stores multiple indicators in a single transaction.
    ///
    /// This is optimized for bulk data loading and reduces database overhead.
    ///
    /// # Arguments
    ///
    /// * `indicators` - Slice of technical indicators to store
    ///
    /// # Returns
    ///
    /// `Ok(())` on success, or a `MarketDataError` if any operation fails
    ///
    /// # Errors
    ///
    /// - `MarketDataError::InvalidSymbol` if any symbol is invalid
    /// - `MarketDataError::Database` if the database transaction fails
    async fn store_indicators(&self, indicators: &[TechnicalIndicator]) -> MarketDataResult<()>;

    /// Get the latest indicator value for a symbol and type
    ///
    /// Retrieves the most recent indicator value for the specified symbol
    /// and indicator type, ordered by timestamp.
    ///
    /// # Arguments
    ///
    /// * `symbol` - Trading symbol to query
    /// * `indicator_type` - Type of technical indicator
    ///
    /// # Returns
    ///
    /// `Some(indicator)` if found, `None` if no data exists, or a `MarketDataError`
    ///
    /// # Errors
    ///
    /// - `MarketDataError::InvalidSymbol` if the symbol is invalid
    /// - `MarketDataError::Database` if the query fails
    async fn get_latest_indicator(
        &self,
        symbol: &str,
        indicator_type: IndicatorType,
    ) -> MarketDataResult<Option<TechnicalIndicator>>;

    /// Get indicator history for a symbol and type within a time range
    ///
    /// Retrieves historical indicator values within the specified time range,
    /// ordered chronologically. This is useful for backtesting and analysis.
    ///
    /// # Arguments
    ///
    /// * `symbol` - Trading symbol to query
    /// * `indicator_type` - Type of technical indicator
    ///
    /// * `from` - Start of time range (inclusive)
    /// * `to` - End of time range (inclusive)
    ///
    /// # Returns
    ///
    /// Vector of indicators ordered by timestamp, or a `MarketDataError`
    ///
    /// # Errors
    ///
    /// - `MarketDataError::InvalidSymbol` if the symbol is invalid
    /// - `MarketDataError::InvalidTimeRange` if from >= to
    ///
    /// - `MarketDataError::Database` if the query fails
    async fn get_indicator_history(
        &self,
        symbol: &str,
        indicator_type: IndicatorType,
        from: DateTime<Utc>,
        to: DateTime<Utc>,
    ) -> MarketDataResult<Vec<TechnicalIndicator>>;

    /// Get all latest indicators for a symbol
    ///
    /// Retrieves the most recent value for each indicator type available
    /// for the specified symbol. Returns a map for easy lookup by indicator type.
    ///
    /// # Arguments
    ///
    /// * `symbol` - Trading symbol to query
    ///
    /// # Returns
    ///
    /// HashMap mapping indicator types to their latest values, or a `MarketDataError`
    ///
    /// # Errors
    ///
    /// - `MarketDataError::InvalidSymbol` if the symbol is invalid
    /// - `MarketDataError::Database` if the query fails
    async fn get_latest_indicators_for_symbol(
        &self,
        symbol: &str,
    ) -> MarketDataResult<HashMap<IndicatorType, TechnicalIndicator>>;

    /// Get indicators for multiple symbols and a specific type
    ///
    /// Efficiently retrieves the latest indicator values for multiple symbols
    /// of the same indicator type. Useful for portfolio analysis and screening.
    ///
    /// # Arguments
    ///
    /// * `symbols` - List of trading symbols to query
    /// * `indicator_type` - Type of technical indicator
    ///
    /// # Returns
    ///
    /// HashMap mapping symbols to their latest indicator values, or a `MarketDataError`
    ///
    /// # Errors
    ///
    /// - `MarketDataError::InvalidSymbol` if any symbol is invalid
    /// - `MarketDataError::Database` if the query fails
    async fn get_indicators_for_symbols(
        &self,
        symbols: &[String],
        indicator_type: IndicatorType,
    ) -> MarketDataResult<HashMap<String, TechnicalIndicator>>;

    /// Get all indicator types available for a symbol
    ///
    /// Returns a list of all indicator types that have data stored
    /// for the specified symbol. Useful for discovering available analysis.
    ///
    /// # Arguments
    ///
    /// * `symbol` - Trading symbol to query
    ///
    /// # Returns
    ///
    /// Vector of available indicator types, or a `MarketDataError`
    ///
    /// # Errors
    ///
    /// - `MarketDataError::InvalidSymbol` if the symbol is invalid
    /// - `MarketDataError::Database` if the query fails
    async fn get_available_indicator_types(
        &self,
        symbol: &str,
    ) -> MarketDataResult<Vec<IndicatorType>>;

    /// Delete old indicator data before a given timestamp
    ///
    /// Removes historical indicator data older than the specified timestamp.
    ///
    /// This is useful for data retention management and storage optimization.
    ///
    /// # Arguments
    ///
    /// * `before` - Timestamp before which all data will be deleted
    ///
    /// # Returns
    ///
    /// Number of records deleted, or a `MarketDataError`
    ///
    /// # Errors
    ///
    /// - `MarketDataError::Database` if the deletion fails
    async fn cleanup_old_indicators(&self, before: DateTime<Utc>) -> MarketDataResult<u64>;

    /// Get indicator statistics (min, max, avg) over a time period
    ///
    /// Calculates statistical summary of indicator values within the specified
    /// time range. Useful for analysis and understanding indicator behavior.
    ///
    /// # Arguments
    ///
    /// * `symbol` - Trading symbol to analyze
    /// * `indicator_type` - Type of technical indicator
    ///
    /// * `from` - Start of analysis period
    /// * `to` - End of analysis period
    ///
    /// # Returns
    ///
    /// `Some(statistics)` if data exists, `None` if no data, or a `MarketDataError`
    ///
    /// # Errors
    ///
    /// - `MarketDataError::InvalidSymbol` if the symbol is invalid
    /// - `MarketDataError::InvalidTimeRange` if from >= to
    ///
    /// - `MarketDataError::Database` if the query fails
    async fn get_indicator_statistics(
        &self,
        symbol: &str,
        indicator_type: IndicatorType,
        from: DateTime<Utc>,
        to: DateTime<Utc>,
    ) -> MarketDataResult<Option<IndicatorStatistics>>;
}

/// Statistical summary of indicator values
///
/// Provides comprehensive statistics for technical indicator values
/// over a specified time period, including distribution metrics
/// and temporal boundaries.
#[derive(Debug, Clone)]
pub struct IndicatorStatistics {
    /// Trading symbol these statistics apply to
    pub symbol: String,
    /// Type of technical indicator analyzed
    pub indicator_type: IndicatorType,
    /// Number of data points in the analysis
    pub count: i64,
    /// Minimum indicator value in the period
    pub min_value: Decimal,
    /// Maximum indicator value in the period
    pub max_value: Decimal,
    /// Average indicator value in the period
    pub avg_value: Decimal,
    /// Timestamp of the first data point
    pub first_timestamp: DateTime<Utc>,
    /// Timestamp of the last data point
    pub last_timestamp: DateTime<Utc>,
}

/// PostgreSQL implementation of IndicatorRepository
///
/// Provides a production-ready implementation of the `IndicatorRepository` trait
/// using PostgreSQL as the backend storage. This implementation is optimized
/// for time-series data with appropriate indexing and query patterns.
///
/// ## Features
///
/// - Transactional batch operations
/// - Optimized time-series queries
///
/// - Input validation and error handling
/// - Conflict resolution with upsert semantics
pub struct PostgresIndicatorRepository {
    /// PostgreSQL connection pool for database operations
    pool: PgPool,
}

impl PostgresIndicatorRepository {
    /// Create a new PostgreSQL indicator repository
    ///
    /// # Arguments
    ///
    /// * `pool` - PostgreSQL connection pool
    ///
    /// # Returns
    ///
    /// A new `PostgresIndicatorRepository` instance
    pub fn new(pool: PgPool) -> Self {
        Self { pool }
    }

    /// Validate that a symbol meets format requirements
    ///
    /// Ensures the symbol is non-empty and within length limits.
    ///
    /// # Arguments
    ///
    /// * `symbol` - Symbol to validate
    ///
    /// # Returns
    ///
    /// `Ok(())` if valid, `MarketDataError::InvalidSymbol` otherwise
    async fn validate_symbol(&self, symbol: &str) -> MarketDataResult<()> {
        if symbol.is_empty() || symbol.len() > 20 {
            return Err(MarketDataError::InvalidSymbol {
                symbol: symbol.to_string(),
            });
        }
        Ok(())
    }

    /// Validate that a time range is logically correct
    ///
    /// Ensures the start time is before the end time.
    ///
    /// # Arguments
    ///
    /// * `from` - Start time
    /// * `to` - End time
    ///
    /// # Returns
    ///
    /// `Ok(())` if valid, `MarketDataError::InvalidTimeRange` otherwise
    async fn validate_time_range(
        &self,
        from: DateTime<Utc>,
        to: DateTime<Utc>,
    ) -> MarketDataResult<()> {
        if from >= to {
            return Err(MarketDataError::InvalidTimeRange { from, to });
        }
        Ok(())
    }
}

#[async_trait]
impl IndicatorRepository for PostgresIndicatorRepository {
    async fn store_indicator(&self, indicator: &TechnicalIndicator) -> MarketDataResult<()> {
        self.validate_symbol(&indicator.symbol).await?;

        sqlx::query(
            r#"
            INSERT INTO technical_indicators (id, symbol, indicator_type, timestamp, value, parameters, created_at)
            VALUES ($1, $2, $3, $4, $5, $6, $7)
            ON CONFLICT (symbol, indicator_type, timestamp) DO UPDATE SET
                value = EXCLUDED.value,
                parameters = EXCLUDED.parameters
            "#
        )
        .bind(indicator.id)
        .bind(&indicator.symbol)
        .bind(indicator.indicator_type as IndicatorType)
        .bind(indicator.timestamp)
        .bind(indicator.value)
        .bind(&indicator.parameters)
        .bind(indicator.created_at)
        .execute(&self.pool)
        .await?;

        Ok(())
    }

    async fn store_indicators(&self, indicators: &[TechnicalIndicator]) -> MarketDataResult<()> {
        if indicators.is_empty() {
            return Ok(());
        }

        // Validate all symbols first
        for indicator in indicators {
            self.validate_symbol(&indicator.symbol).await?;
        }

        let mut tx = self.pool.begin().await?;

        for indicator in indicators {
            sqlx::query(
                r#"
                INSERT INTO technical_indicators (id, symbol, indicator_type, timestamp, value, parameters, created_at)
                VALUES ($1, $2, $3, $4, $5, $6, $7)
                ON CONFLICT (symbol, indicator_type, timestamp) DO UPDATE SET
                    value = EXCLUDED.value,
                    parameters = EXCLUDED.parameters
                "#
            )
            .bind(indicator.id)
            .bind(&indicator.symbol)
            .bind(indicator.indicator_type as IndicatorType)
            .bind(indicator.timestamp)
            .bind(indicator.value)
            .bind(&indicator.parameters)
            .bind(indicator.created_at)
            .execute(&mut *tx)
            .await?;
        }

        tx.commit().await?;
        Ok(())
    }

    async fn get_latest_indicator(
        &self,
        symbol: &str,
        indicator_type: IndicatorType,
    ) -> MarketDataResult<Option<TechnicalIndicator>> {
        self.validate_symbol(symbol).await?;

        let row = sqlx::query(
            r#"
            SELECT id, symbol, indicator_type, timestamp, value, parameters, created_at
            FROM technical_indicators
            WHERE symbol = $1 AND indicator_type = $2
            ORDER BY timestamp DESC
            LIMIT 1
            "#,
        )
        .bind(symbol)
        .bind(indicator_type as IndicatorType)
        .fetch_optional(&self.pool)
        .await?;

        if let Some(row) = row {
            Ok(Some(TechnicalIndicator {
                id: row.get("id"),
                symbol: row.get("symbol"),
                indicator_type: row.get("indicator_type"),
                timestamp: row.get("timestamp"),
                value: row.get("value"),
                parameters: row.get("parameters"),
                created_at: row.get("created_at"),
            }))
        } else {
            Ok(None)
        }
    }

    async fn get_indicator_history(
        &self,
        symbol: &str,
        indicator_type: IndicatorType,
        from: DateTime<Utc>,
        to: DateTime<Utc>,
    ) -> MarketDataResult<Vec<TechnicalIndicator>> {
        self.validate_symbol(symbol).await?;
        self.validate_time_range(from, to).await?;

        let rows = sqlx::query(
            r#"
            SELECT id, symbol, indicator_type, timestamp, value, parameters, created_at
            FROM technical_indicators
            WHERE symbol = $1 AND indicator_type = $2 AND timestamp >= $3 AND timestamp <= $4
            ORDER BY timestamp ASC
            "#,
        )
        .bind(symbol)
        .bind(indicator_type as IndicatorType)
        .bind(from)
        .bind(to)
        .fetch_all(&self.pool)
        .await?;

        let indicators = rows
            .into_iter()
            .map(|row| TechnicalIndicator {
                id: row.get("id"),
                symbol: row.get("symbol"),
                indicator_type: row.get("indicator_type"),
                timestamp: row.get("timestamp"),
                value: row.get("value"),
                parameters: row.get("parameters"),
                created_at: row.get("created_at"),
            })
            .collect();

        Ok(indicators)
    }

    async fn get_latest_indicators_for_symbol(
        &self,
        symbol: &str,
    ) -> MarketDataResult<HashMap<IndicatorType, TechnicalIndicator>> {
        self.validate_symbol(symbol).await?;

        let rows = sqlx::query(
            r#"
            SELECT DISTINCT ON (indicator_type) id, symbol, indicator_type, timestamp, value, parameters, created_at
            FROM technical_indicators
            WHERE symbol = $1
            ORDER BY indicator_type, timestamp DESC
            "#
        )
        .bind(symbol)
        .fetch_all(&self.pool)
        .await?;

        let mut indicators = HashMap::new();
        for row in rows {
            let indicator = TechnicalIndicator {
                id: row.get("id"),
                symbol: row.get("symbol"),
                indicator_type: row.get("indicator_type"),
                timestamp: row.get("timestamp"),
                value: row.get("value"),
                parameters: row.get("parameters"),
                created_at: row.get("created_at"),
            };
            indicators.insert(indicator.indicator_type, indicator);
        }

        Ok(indicators)
    }

    async fn get_indicators_for_symbols(
        &self,
        symbols: &[String],
        indicator_type: IndicatorType,
    ) -> MarketDataResult<HashMap<String, TechnicalIndicator>> {
        if symbols.is_empty() {
            return Ok(HashMap::new());
        }

        // Validate all symbols
        for symbol in symbols {
            self.validate_symbol(symbol).await?;
        }

        let rows = sqlx::query(
            r#"
            SELECT DISTINCT ON (symbol) id, symbol, indicator_type, timestamp, value, parameters, created_at
            FROM technical_indicators
            WHERE symbol = ANY($1) AND indicator_type = $2
            ORDER BY symbol, timestamp DESC
            "#
        )
        .bind(symbols)
        .bind(indicator_type as IndicatorType)
        .fetch_all(&self.pool)
        .await?;

        let mut indicators = HashMap::new();
        for row in rows {
            let symbol: String = row.get("symbol");
            let indicator = TechnicalIndicator {
                id: row.get("id"),
                symbol: symbol.clone(),
                indicator_type: row.get("indicator_type"),
                timestamp: row.get("timestamp"),
                value: row.get("value"),
                parameters: row.get("parameters"),
                created_at: row.get("created_at"),
            };
            indicators.insert(symbol, indicator);
        }

        Ok(indicators)
    }

    async fn get_available_indicator_types(
        &self,
        symbol: &str,
    ) -> MarketDataResult<Vec<IndicatorType>> {
        self.validate_symbol(symbol).await?;

        let rows = sqlx::query(
            "SELECT DISTINCT indicator_type FROM technical_indicators WHERE symbol = $1",
        )
        .bind(symbol)
        .fetch_all(&self.pool)
        .await?;

        let types = rows
            .into_iter()
            .map(|row| row.get("indicator_type"))
            .collect();

        Ok(types)
    }

    async fn cleanup_old_indicators(&self, before: DateTime<Utc>) -> MarketDataResult<u64> {
        let result = sqlx::query("DELETE FROM technical_indicators WHERE timestamp < $1")
            .bind(before)
            .execute(&self.pool)
            .await?;

        Ok(result.rows_affected())
    }

    async fn get_indicator_statistics(
        &self,
        symbol: &str,
        indicator_type: IndicatorType,
        from: DateTime<Utc>,
        to: DateTime<Utc>,
    ) -> MarketDataResult<Option<IndicatorStatistics>> {
        self.validate_symbol(symbol).await?;
        self.validate_time_range(from, to).await?;

        let row = sqlx::query(
            r#"
            SELECT 
                COUNT(*) as count,
                MIN(value) as min_value,
                MAX(value) as max_value,
                AVG(value) as avg_value,
                MIN(timestamp) as first_timestamp,
                MAX(timestamp) as last_timestamp
            FROM technical_indicators
            WHERE symbol = $1 AND indicator_type = $2 AND timestamp >= $3 AND timestamp <= $4
            "#,
        )
        .bind(symbol)
        .bind(indicator_type as IndicatorType)
        .bind(from)
        .bind(to)
        .fetch_one(&self.pool)
        .await?;

        let count: i64 = row.get("count");
        if count > 0 {
            Ok(Some(IndicatorStatistics {
                symbol: symbol.to_string(),
                indicator_type,
                count,
                min_value: row
                    .get::<Option<Decimal>, _>("min_value")
                    .unwrap_or_default(),
                max_value: row
                    .get::<Option<Decimal>, _>("max_value")
                    .unwrap_or_default(),
                avg_value: row
                    .get::<Option<Decimal>, _>("avg_value")
                    .unwrap_or_default(),
                first_timestamp: row
                    .get::<Option<DateTime<Utc>>, _>("first_timestamp")
                    .unwrap_or(from),
                last_timestamp: row
                    .get::<Option<DateTime<Utc>>, _>("last_timestamp")
                    .unwrap_or(to),
            }))
        } else {
            Ok(None)
        }
    }
}
