//! Strategy Coordination Logic
//!
//! Manages multiple trading strategies and their lifecycle.

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use std::collections::HashMap;
use thiserror::Error;
use tracing::{debug, error, info, instrument, warn};

/// Strategy coordinator for managing strategy lifecycle
#[derive(Debug, Clone)]
pub struct StrategyCoordinator {
    pool: PgPool,
}

/// Strategy types supported by the trading agent
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::Type)]
#[sqlx(type_name = "text")]
pub enum StrategyType {
    EqualWeight,
    RiskParity,
    MLOptimized,
    MeanVariance,
    Momentum,
    MeanReversion,
}

impl std::fmt::Display for StrategyType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            StrategyType::EqualWeight => write!(f, "EqualWeight"),
            StrategyType::RiskParity => write!(f, "RiskParity"),
            StrategyType::MLOptimized => write!(f, "MLOptimized"),
            StrategyType::MeanVariance => write!(f, "MeanVariance"),
            StrategyType::Momentum => write!(f, "Momentum"),
            StrategyType::MeanReversion => write!(f, "MeanReversion"),
        }
    }
}

impl std::str::FromStr for StrategyType {
    type Err = StrategyError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "EqualWeight" => Ok(StrategyType::EqualWeight),
            "RiskParity" => Ok(StrategyType::RiskParity),
            "MLOptimized" => Ok(StrategyType::MLOptimized),
            "MeanVariance" => Ok(StrategyType::MeanVariance),
            "Momentum" => Ok(StrategyType::Momentum),
            "MeanReversion" => Ok(StrategyType::MeanReversion),
            _ => Err(StrategyError::InvalidStrategyType(s.to_string())),
        }
    }
}

/// Strategy status
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::Type)]
#[sqlx(type_name = "text")]
pub enum StrategyStatus {
    Active,
    Paused,
    Stopped,
}

impl std::fmt::Display for StrategyStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            StrategyStatus::Active => write!(f, "Active"),
            StrategyStatus::Paused => write!(f, "Paused"),
            StrategyStatus::Stopped => write!(f, "Stopped"),
        }
    }
}

impl std::str::FromStr for StrategyStatus {
    type Err = StrategyError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "Active" => Ok(StrategyStatus::Active),
            "Paused" => Ok(StrategyStatus::Paused),
            "Stopped" => Ok(StrategyStatus::Stopped),
            _ => Err(StrategyError::InvalidStatus(s.to_string())),
        }
    }
}

/// Strategy configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StrategyConfig {
    pub strategy_id: String,
    pub strategy_name: String,
    pub strategy_type: StrategyType,
    pub parameters: HashMap<String, f64>,
    pub status: StrategyStatus,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

/// Strategy-related errors
#[derive(Debug, Error)]
pub enum StrategyError {
    #[error("Database error: {0}")]
    Database(#[from] sqlx::Error),

    #[error("Strategy not found: {0}")]
    NotFound(String),

    #[error("Invalid strategy type: {0}")]
    InvalidStrategyType(String),

    #[error("Invalid status: {0}")]
    InvalidStatus(String),

    #[error("Strategy name already exists: {0}")]
    DuplicateName(String),

    #[error("JSON serialization error: {0}")]
    JsonError(#[from] serde_json::Error),
}

impl StrategyCoordinator {
    /// Create a new strategy coordinator
    pub fn new(pool: PgPool) -> Self {
        Self { pool }
    }

    /// Register a new strategy
    #[instrument(skip(self, config), fields(strategy_name = %config.strategy_name, strategy_type = ?config.strategy_type))]
    pub async fn register_strategy(&self, config: StrategyConfig) -> Result<String, StrategyError> {
        info!(
            strategy_name = %config.strategy_name,
            strategy_type = ?config.strategy_type,
            "Registering new strategy"
        );

        // Serialize parameters to JSONB
        let parameters_json = serde_json::to_value(&config.parameters)?;

        // Insert strategy into database
        let result = sqlx::query!(
            r#"
            INSERT INTO strategy_configs (strategy_name, strategy_type, parameters, status)
            VALUES ($1, $2, $3, $4)
            RETURNING strategy_id
            "#,
            config.strategy_name,
            config.strategy_type.to_string(),
            parameters_json,
            config.status.to_string()
        )
        .fetch_one(&self.pool)
        .await
        .map_err(|e| {
            if let sqlx::Error::Database(ref db_err) = e {
                if db_err.is_unique_violation() {
                    error!(
                        strategy_name = %config.strategy_name,
                        "Strategy name already exists"
                    );
                    return StrategyError::DuplicateName(config.strategy_name.clone());
                }
            }
            error!(error = ?e, "Failed to register strategy");
            StrategyError::Database(e)
        })?;

        let strategy_id = result.strategy_id.to_string();

        info!(
            strategy_id = %strategy_id,
            strategy_name = %config.strategy_name,
            "Strategy registered successfully"
        );

        Ok(strategy_id)
    }

    /// List all strategies
    #[instrument(skip(self))]
    pub async fn list_strategies(&self) -> Result<Vec<StrategyConfig>, StrategyError> {
        debug!("Listing all strategies");

        let rows = sqlx::query!(
            r#"
            SELECT
                strategy_id,
                strategy_name,
                strategy_type,
                parameters,
                status,
                created_at,
                updated_at
            FROM strategy_configs
            ORDER BY created_at DESC
            "#
        )
        .fetch_all(&self.pool)
        .await
        .map_err(|e| {
            error!(error = ?e, "Failed to list strategies");
            StrategyError::Database(e)
        })?;

        let strategies: Vec<StrategyConfig> = rows
            .into_iter()
            .map(|row| {
                let parameters: HashMap<String, f64> =
                    serde_json::from_value(row.parameters).unwrap_or_default();

                let strategy_type = row.strategy_type.parse().unwrap_or_else(|e| {
                    warn!(
                        error = ?e,
                        strategy_type = %row.strategy_type,
                        "Failed to parse strategy type, defaulting to EqualWeight"
                    );
                    StrategyType::EqualWeight
                });

                let status = row.status.parse().unwrap_or_else(|e| {
                    warn!(
                        error = ?e,
                        status = %row.status,
                        "Failed to parse status, defaulting to Active"
                    );
                    StrategyStatus::Active
                });

                StrategyConfig {
                    strategy_id: row.strategy_id.to_string(),
                    strategy_name: row.strategy_name,
                    strategy_type,
                    parameters,
                    status,
                    created_at: row.created_at,
                    updated_at: row.updated_at,
                }
            })
            .collect();

        debug!(count = strategies.len(), "Retrieved strategies");

        Ok(strategies)
    }

    /// Update strategy status
    #[instrument(skip(self), fields(strategy_id = %strategy_id, new_status = ?status))]
    pub async fn update_status(
        &self,
        strategy_id: &str,
        status: StrategyStatus,
    ) -> Result<(), StrategyError> {
        info!(
            strategy_id = %strategy_id,
            new_status = ?status,
            "Updating strategy status"
        );

        // Parse UUID
        let uuid = uuid::Uuid::parse_str(strategy_id).map_err(|e| {
            error!(
                strategy_id = %strategy_id,
                error = ?e,
                "Invalid strategy ID format"
            );
            StrategyError::NotFound(strategy_id.to_string())
        })?;

        let result = sqlx::query!(
            r#"
            UPDATE strategy_configs
            SET status = $1
            WHERE strategy_id = $2
            "#,
            status.to_string(),
            uuid
        )
        .execute(&self.pool)
        .await
        .map_err(|e| {
            error!(error = ?e, "Failed to update strategy status");
            StrategyError::Database(e)
        })?;

        if result.rows_affected() == 0 {
            error!(
                strategy_id = %strategy_id,
                "Strategy not found for status update"
            );
            return Err(StrategyError::NotFound(strategy_id.to_string()));
        }

        info!(
            strategy_id = %strategy_id,
            new_status = ?status,
            "Strategy status updated successfully"
        );

        Ok(())
    }

    /// Get active strategies only
    #[instrument(skip(self))]
    pub async fn get_active_strategies(&self) -> Result<Vec<StrategyConfig>, StrategyError> {
        debug!("Fetching active strategies");

        let rows = sqlx::query!(
            r#"
            SELECT
                strategy_id,
                strategy_name,
                strategy_type,
                parameters,
                status,
                created_at,
                updated_at
            FROM strategy_configs
            WHERE status = 'Active'
            ORDER BY created_at DESC
            "#
        )
        .fetch_all(&self.pool)
        .await
        .map_err(|e| {
            error!(error = ?e, "Failed to fetch active strategies");
            StrategyError::Database(e)
        })?;

        let strategies: Vec<StrategyConfig> = rows
            .into_iter()
            .map(|row| {
                let parameters: HashMap<String, f64> =
                    serde_json::from_value(row.parameters).unwrap_or_default();

                let strategy_type = row
                    .strategy_type
                    .parse()
                    .unwrap_or(StrategyType::EqualWeight);
                let status = row.status.parse().unwrap_or(StrategyStatus::Active);

                StrategyConfig {
                    strategy_id: row.strategy_id.to_string(),
                    strategy_name: row.strategy_name,
                    strategy_type,
                    parameters,
                    status,
                    created_at: row.created_at,
                    updated_at: row.updated_at,
                }
            })
            .collect();

        debug!(count = strategies.len(), "Retrieved active strategies");

        Ok(strategies)
    }

    /// Get strategy by ID
    #[instrument(skip(self), fields(strategy_id = %strategy_id))]
    pub async fn get_strategy(&self, strategy_id: &str) -> Result<StrategyConfig, StrategyError> {
        debug!(strategy_id = %strategy_id, "Fetching strategy by ID");

        // Parse UUID
        let uuid = uuid::Uuid::parse_str(strategy_id).map_err(|e| {
            error!(
                strategy_id = %strategy_id,
                error = ?e,
                "Invalid strategy ID format"
            );
            StrategyError::NotFound(strategy_id.to_string())
        })?;

        let row = sqlx::query!(
            r#"
            SELECT
                strategy_id,
                strategy_name,
                strategy_type,
                parameters,
                status,
                created_at,
                updated_at
            FROM strategy_configs
            WHERE strategy_id = $1
            "#,
            uuid
        )
        .fetch_optional(&self.pool)
        .await
        .map_err(|e| {
            error!(error = ?e, "Failed to fetch strategy");
            StrategyError::Database(e)
        })?;

        match row {
            Some(row) => {
                let parameters: HashMap<String, f64> =
                    serde_json::from_value(row.parameters).unwrap_or_default();

                let strategy_type = row
                    .strategy_type
                    .parse()
                    .unwrap_or(StrategyType::EqualWeight);
                let status = row.status.parse().unwrap_or(StrategyStatus::Active);

                let config = StrategyConfig {
                    strategy_id: row.strategy_id.to_string(),
                    strategy_name: row.strategy_name,
                    strategy_type,
                    parameters,
                    status,
                    created_at: row.created_at,
                    updated_at: row.updated_at,
                };

                debug!(strategy_id = %strategy_id, "Strategy retrieved successfully");

                Ok(config)
            },
            None => {
                error!(strategy_id = %strategy_id, "Strategy not found");
                Err(StrategyError::NotFound(strategy_id.to_string()))
            },
        }
    }
}

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

    #[test]
    fn test_strategy_type_display() {
        assert_eq!(StrategyType::EqualWeight.to_string(), "EqualWeight");
        assert_eq!(StrategyType::RiskParity.to_string(), "RiskParity");
        assert_eq!(StrategyType::MLOptimized.to_string(), "MLOptimized");
    }

    #[test]
    fn test_strategy_type_from_str() {
        assert_eq!(
            "EqualWeight".parse::<StrategyType>().unwrap(),
            StrategyType::EqualWeight
        );
        assert_eq!(
            "RiskParity".parse::<StrategyType>().unwrap(),
            StrategyType::RiskParity
        );
        assert!("Invalid".parse::<StrategyType>().is_err());
    }

    #[test]
    fn test_strategy_status_display() {
        assert_eq!(StrategyStatus::Active.to_string(), "Active");
        assert_eq!(StrategyStatus::Paused.to_string(), "Paused");
        assert_eq!(StrategyStatus::Stopped.to_string(), "Stopped");
    }

    #[test]
    fn test_strategy_status_from_str() {
        assert_eq!(
            "Active".parse::<StrategyStatus>().unwrap(),
            StrategyStatus::Active
        );
        assert_eq!(
            "Paused".parse::<StrategyStatus>().unwrap(),
            StrategyStatus::Paused
        );
        assert!("Invalid".parse::<StrategyStatus>().is_err());
    }
}
