//! Position repository implementation
//!
//! This module provides repository pattern implementation for trading positions
//! with `PostgreSQL` backing. It includes real-time position tracking, P&L calculations,
//! and position management operations for high-frequency trading.

use async_trait::async_trait;
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
// Removed direct rust_decimal import - using common::Decimal via common crate
use sqlx::{Pool, Postgres, Row};
use uuid::Uuid;

use crate::{Repository, RepositoryError, Result};

// Import Position directly from common
use common::Position;

/// Filter criteria for position queries
#[derive(Debug, Default, Clone)]
pub struct PositionFilter {
    /// Filter by symbol
    pub symbol: Option<String>,

    /// Filter positions with quantity greater than this value
    pub min_quantity: Option<Decimal>,

    /// Filter positions with quantity less than this value
    pub max_quantity: Option<Decimal>,

    /// Filter by position type (long/short/flat)
    pub position_type: Option<PositionType>,

    /// Filter positions with unrealized P&L above this threshold
    pub min_unrealized_pnl: Option<Decimal>,

    /// Filter positions with unrealized P&L below this threshold
    pub max_unrealized_pnl: Option<Decimal>,

    /// Filter positions created after this timestamp
    pub created_after: Option<DateTime<Utc>>,

    /// Filter positions created before this timestamp
    pub created_before: Option<DateTime<Utc>>,

    /// Include only positions with current price data
    pub has_current_price: Option<bool>,

    /// Limit number of results
    pub limit: Option<i64>,

    /// Offset for pagination
    pub offset: Option<i64>,
}

/// Position type for filtering and categorization
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PositionType {
    /// Long position (quantity > 0)
    Long,
    /// Short position (quantity < 0)
    Short,
    /// Flat position (quantity = 0)
    Flat,
}

impl PositionFilter {
    /// Create a new empty filter
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Filter by symbol
    #[must_use]
    pub fn symbol<S: Into<String>>(mut self, symbol: S) -> Self {
        self.symbol = Some(symbol.into());
        self
    }

    /// Filter by position type
    #[must_use]
    pub fn position_type(mut self, position_type: PositionType) -> Self {
        self.position_type = Some(position_type);
        self
    }

    /// Filter by quantity range
    #[must_use]
    pub fn quantity_range(mut self, min: Option<Decimal>, max: Option<Decimal>) -> Self {
        self.min_quantity = min;
        self.max_quantity = max;
        self
    }

    /// Filter by P&L range
    #[must_use]
    pub fn pnl_range(mut self, min: Option<Decimal>, max: Option<Decimal>) -> Self {
        self.min_unrealized_pnl = min;
        self.max_unrealized_pnl = max;
        self
    }

    /// Filter by date range
    #[must_use]
    pub fn created_between(mut self, start: DateTime<Utc>, end: DateTime<Utc>) -> Self {
        self.created_after = Some(start);
        self.created_before = Some(end);
        self
    }

    /// Filter only positions with current price data
    #[must_use]
    pub fn with_current_price(mut self) -> Self {
        self.has_current_price = Some(true);
        self
    }

    /// Limit results
    #[must_use]
    pub fn limit(mut self, limit: i64) -> Self {
        self.limit = Some(limit);
        self
    }

    /// Set offset for pagination
    #[must_use]
    pub fn offset(mut self, offset: i64) -> Self {
        self.offset = Some(offset);
        self
    }
}

/// Position repository trait defining available operations
#[async_trait]
pub trait PositionRepository: Repository<Position, Uuid> {
    /// Find positions matching the filter criteria
    async fn find_by_filter(&self, filter: &PositionFilter) -> Result<Vec<Position>>;

    /// Find position by symbol (there should typically be only one per symbol)
    async fn find_by_symbol(&self, symbol: &str) -> Result<Option<Position>>;

    /// Find all positions with non-zero quantity
    async fn find_active_positions(&self) -> Result<Vec<Position>>;

    /// Find positions by type (long/short/flat)
    async fn find_by_type(&self, position_type: PositionType) -> Result<Vec<Position>>;

    /// Update position quantity and recalculate averages
    async fn update_position(
        &self,
        symbol: &str,
        quantity_delta: Decimal,
        price: Decimal,
    ) -> Result<Position>;

    /// Update position's current price and recalculate unrealized P&L
    async fn update_market_price(&self, symbol: &str, current_price: Decimal) -> Result<()>;

    /// Batch update market prices for multiple positions
    async fn batch_update_prices(
        &self,
        price_updates: &[(String, Decimal)],
    ) -> Result<Vec<Position>>;

    /// Close position (set quantity to zero, realize P&L)
    async fn close_position(&self, symbol: &str, closing_price: Decimal) -> Result<Position>;

    /// Get position statistics
    async fn get_position_stats(&self) -> Result<PositionStats>;

    /// Get positions with high risk (based on P&L thresholds)
    async fn find_high_risk_positions(&self, pnl_threshold: Decimal) -> Result<Vec<Position>>;

    /// Calculate total portfolio P&L
    async fn calculate_total_pnl(&self) -> Result<PortfolioPnL>;

    /// Get positions ready for margin calls
    async fn find_margin_call_positions(&self, margin_threshold: Decimal) -> Result<Vec<Position>>;
}

/// Position statistics for portfolio analysis
#[derive(Debug, Clone)]
pub struct PositionStats {
    /// Total number of positions across all symbols
    pub total_positions: i64,
    /// Number of long positions (quantity > 0)
    pub long_positions: i64,
    /// Number of short positions (quantity < 0)
    pub short_positions: i64,
    /// Number of flat positions (quantity = 0)
    pub flat_positions: i64,
    /// Total notional value of all positions
    pub total_notional_value: Decimal,
    /// Total unrealized profit and loss
    pub total_unrealized_pnl: Decimal,
    /// Total realized profit and loss
    pub total_realized_pnl: Decimal,
    /// Largest position by absolute quantity
    pub largest_position: Option<Decimal>,
    /// Symbol with the highest total P&L
    pub most_profitable_symbol: Option<String>,
    /// Symbol with the lowest total P&L
    pub least_profitable_symbol: Option<String>,
}

/// Portfolio profit and loss summary
#[derive(Debug, Clone)]
pub struct PortfolioPnL {
    /// Total unrealized profit and loss across all positions
    pub total_unrealized_pnl: Decimal,
    /// Total realized profit and loss from closed positions
    pub total_realized_pnl: Decimal,
    /// Combined total P&L (realized + unrealized)
    pub total_pnl: Decimal,
    /// Total notional value of all positions
    pub total_notional_value: Decimal,
    /// Total margin requirement for all positions
    pub total_margin_requirement: Decimal,
    /// Return on investment as a percentage
    pub roi_percentage: Decimal,
    /// Number of active positions
    pub position_count: i64,
}

/// `PostgreSQL` implementation of `PositionRepository`
pub struct PostgresPositionRepository {
    pool: Pool<Postgres>,
}

impl PostgresPositionRepository {
    /// Create a new `PostgreSQL` position repository
    #[must_use]
    pub fn new(pool: Pool<Postgres>) -> Self {
        Self { pool }
    }

    /// Helper method to convert `PositionType` to SQL condition
    fn position_type_to_sql_condition(position_type: PositionType) -> &'static str {
        match position_type {
            PositionType::Long => "quantity > 0",
            PositionType::Short => "quantity < 0",
            PositionType::Flat => "quantity = 0",
        }
    }
}

#[async_trait]
impl Repository<Position, Uuid> for PostgresPositionRepository {
    async fn find_by_id(&self, id: &Uuid) -> Result<Option<Position>> {
        let query = r"
            SELECT id, symbol, quantity, avg_price, unrealized_pnl, realized_pnl,
                   created_at, updated_at, current_price, notional_value, margin_requirement
            FROM positions WHERE id = $1
        ";

        let row = sqlx::query(query)
            .bind(id)
            .fetch_optional(&self.pool)
            .await?;

        match row {
            Some(row) => {
                let position = Position {
                    id: row.get("id"),
                    symbol: row.get("symbol"),
                    quantity: row.get("quantity"),
                    avg_price: row.get("avg_price"),
                    avg_cost: row.get("avg_price"), // alias for avg_price
                    basis: row.get("avg_price"),    // cost basis, using avg_price
                    average_price: row.get("avg_price"), // alias for avg_price
                    market_value: row.get("notional_value"), // using notional_value as market_value
                    unrealized_pnl: row.get("unrealized_pnl"),
                    realized_pnl: row.get("realized_pnl"),
                    created_at: row.get("created_at"),
                    updated_at: row.get("updated_at"),
                    last_updated: row.get("updated_at"), // alias for updated_at
                    current_price: row.get("current_price"),
                    notional_value: row.get("notional_value"),
                    margin_requirement: row.get("margin_requirement"),
                };
                Ok(Some(position))
            },
            None => Ok(None),
        }
    }

    async fn save(&self, position: &Position) -> Result<Position> {
        let query = r"
            INSERT INTO positions (id, symbol, quantity, avg_price, unrealized_pnl, realized_pnl,
                                 created_at, updated_at, current_price, notional_value, margin_requirement)
            VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
            ON CONFLICT (id) DO UPDATE SET
                quantity = EXCLUDED.quantity,
                avg_price = EXCLUDED.avg_price,
                unrealized_pnl = EXCLUDED.unrealized_pnl,
                realized_pnl = EXCLUDED.realized_pnl,
                updated_at = EXCLUDED.updated_at,
                current_price = EXCLUDED.current_price,
                notional_value = EXCLUDED.notional_value,
                margin_requirement = EXCLUDED.margin_requirement
            RETURNING *
        ";

        let row = sqlx::query(query)
            .bind(position.id)
            .bind(&position.symbol)
            .bind(position.quantity)
            .bind(position.avg_price)
            .bind(position.unrealized_pnl)
            .bind(position.realized_pnl)
            .bind(position.created_at)
            .bind(position.updated_at)
            .bind(position.current_price)
            .bind(position.notional_value)
            .bind(position.margin_requirement)
            .fetch_one(&self.pool)
            .await?;

        Ok(Position {
            id: row.get("id"),
            symbol: row.get("symbol"),
            quantity: row.get("quantity"),
            avg_price: row.get("avg_price"),
            avg_cost: row.get("avg_price"),      // alias for avg_price
            basis: row.get("avg_price"),         // cost basis, using avg_price
            average_price: row.get("avg_price"), // alias for avg_price
            market_value: row.get("notional_value"), // using notional_value as market_value
            unrealized_pnl: row.get("unrealized_pnl"),
            realized_pnl: row.get("realized_pnl"),
            created_at: row.get("created_at"),
            updated_at: row.get("updated_at"),
            last_updated: row.get("updated_at"), // alias for updated_at
            current_price: row.get("current_price"),
            notional_value: row.get("notional_value"),
            margin_requirement: row.get("margin_requirement"),
        })
    }

    async fn delete(&self, id: &Uuid) -> Result<bool> {
        let result = sqlx::query("DELETE FROM positions WHERE id = $1")
            .bind(id)
            .execute(&self.pool)
            .await?;

        Ok(result.rows_affected() > 0)
    }

    async fn exists(&self, id: &Uuid) -> Result<bool> {
        let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM positions WHERE id = $1")
            .bind(id)
            .fetch_one(&self.pool)
            .await?;

        Ok(count > 0)
    }
}

#[async_trait]
impl PositionRepository for PostgresPositionRepository {
    async fn find_by_filter(&self, filter: &PositionFilter) -> Result<Vec<Position>> {
        use std::fmt::Write;

        let mut conditions = Vec::new();
        let mut param_count = 0;

        let mut query = r"
            SELECT id, symbol, quantity, avg_price, unrealized_pnl, realized_pnl,
                   created_at, updated_at, current_price, notional_value, margin_requirement
            FROM positions
        "
        .to_string();

        // Build WHERE clause dynamically based on filter
        if filter.symbol.is_some() {
            param_count += 1;
            conditions.push(format!("symbol = ${param_count}"));
        }

        if let Some(pos_type) = filter.position_type {
            let condition = Self::position_type_to_sql_condition(pos_type);
            conditions.push(condition.to_string());
        }

        if filter.min_quantity.is_some() {
            param_count += 1;
            conditions.push(format!("ABS(quantity) >= ${param_count}"));
        }

        if filter.max_quantity.is_some() {
            param_count += 1;
            conditions.push(format!("ABS(quantity) <= ${param_count}"));
        }

        if filter.min_unrealized_pnl.is_some() {
            param_count += 1;
            conditions.push(format!("unrealized_pnl >= ${param_count}"));
        }

        if filter.max_unrealized_pnl.is_some() {
            param_count += 1;
            conditions.push(format!("unrealized_pnl <= ${param_count}"));
        }

        if filter.created_after.is_some() {
            param_count += 1;
            conditions.push(format!("created_at >= ${param_count}"));
        }

        if filter.created_before.is_some() {
            param_count += 1;
            conditions.push(format!("created_at <= ${param_count}"));
        }

        if let Some(has_current_price) = filter.has_current_price {
            if has_current_price {
                conditions.push("current_price IS NOT NULL".to_owned());
            } else {
                conditions.push("current_price IS NULL".to_owned());
            }
        }

        if !conditions.is_empty() {
            // Writing to a String never fails
            let _ = write!(query, " WHERE {}", conditions.join(" AND "));
        }

        query.push_str(" ORDER BY updated_at DESC");

        if let Some(_limit) = filter.limit {
            param_count += 1;
            // Writing to a String never fails
            let _ = write!(query, " LIMIT ${param_count}");
        }

        if let Some(_offset) = filter.offset {
            param_count += 1;
            // Writing to a String never fails
            let _ = write!(query, " OFFSET ${param_count}");
        }

        // Execute query with parameters (simplified version)
        let positions = sqlx::query_as::<_, Position>(&query)
            .fetch_all(&self.pool)
            .await?;

        Ok(positions)
    }

    async fn find_by_symbol(&self, symbol: &str) -> Result<Option<Position>> {
        let position = sqlx::query_as::<_, Position>(
            r"
            SELECT id, symbol, quantity, avg_price, unrealized_pnl, realized_pnl,
                   created_at, updated_at, current_price, notional_value, margin_requirement
            FROM positions WHERE symbol = $1 LIMIT 1
            ",
        )
        .bind(symbol)
        .fetch_optional(&self.pool)
        .await?;

        Ok(position)
    }

    async fn find_active_positions(&self) -> Result<Vec<Position>> {
        let positions = sqlx::query_as::<_, Position>(
            r"
            SELECT id, symbol, quantity, avg_price, unrealized_pnl, realized_pnl,
                   created_at, updated_at, current_price, notional_value, margin_requirement
            FROM positions WHERE quantity != 0 ORDER BY ABS(quantity) DESC
            ",
        )
        .fetch_all(&self.pool)
        .await?;

        Ok(positions)
    }

    async fn find_by_type(&self, position_type: PositionType) -> Result<Vec<Position>> {
        let condition = Self::position_type_to_sql_condition(position_type);
        let query = format!(
            r"
            SELECT id, symbol, quantity, avg_price, unrealized_pnl, realized_pnl,
                   created_at, updated_at, current_price, notional_value, margin_requirement
            FROM positions WHERE {condition} ORDER BY ABS(quantity) DESC
            "
        );

        let positions = sqlx::query_as::<_, Position>(&query)
            .fetch_all(&self.pool)
            .await?;

        Ok(positions)
    }

    async fn update_position(
        &self,
        symbol: &str,
        quantity_delta: Decimal,
        price: Decimal,
    ) -> Result<Position> {
        let mut tx = self.pool.begin().await?;

        // First, try to get existing position
        let existing_position = sqlx::query_as::<_, Position>(
            r"
            SELECT id, symbol, quantity, avg_price, unrealized_pnl, realized_pnl,
                   created_at, updated_at, current_price, notional_value, margin_requirement
            FROM positions WHERE symbol = $1 FOR UPDATE
            ",
        )
        .bind(symbol)
        .fetch_optional(&mut *tx)
        .await?;

        let updated_position = if let Some(mut position) = existing_position {
            // Update existing position
            let old_quantity = position.quantity;
            let new_quantity = old_quantity + quantity_delta;

            // Calculate new average price
            let new_avg_price = if new_quantity.is_zero() {
                position.avg_price // Keep old average when closing
            } else if old_quantity.is_zero() {
                price // New position
            } else if (old_quantity > Decimal::ZERO) == (quantity_delta > Decimal::ZERO) {
                // Adding to existing position (same side)
                let total_cost = old_quantity * position.avg_price + quantity_delta * price;
                total_cost / new_quantity
            } else {
                // Reducing position or switching sides
                if new_quantity.abs() < old_quantity.abs() {
                    position.avg_price // Keep average when reducing
                } else {
                    price // New average when switching sides
                }
            };

            position.quantity = new_quantity;
            position.avg_price = new_avg_price;
            position.notional_value = new_quantity.abs() * new_avg_price;
            position.margin_requirement =
                position.notional_value * Decimal::new(2, 2); // 0.02
            position.updated_at = Utc::now();

            // Update in database
            sqlx::query(
                r"
                UPDATE positions SET 
                    quantity = $1, 
                    avg_price = $2, 
                    notional_value = $3,
                    margin_requirement = $4,
                    updated_at = $5
                WHERE symbol = $6
                ",
            )
            .bind(position.quantity)
            .bind(position.avg_price)
            .bind(position.notional_value)
            .bind(position.margin_requirement)
            .bind(position.updated_at)
            .bind(symbol)
            .execute(&mut *tx)
            .await?;

            position
        } else {
            // Create new position
            let position = Position::new(symbol.to_string(), quantity_delta, price);

            sqlx::query(
                r"
                INSERT INTO positions (id, symbol, quantity, avg_price, unrealized_pnl, realized_pnl,
                                     created_at, updated_at, current_price, notional_value, margin_requirement)
                VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
                "
            )
            .bind(position.id)
            .bind(&position.symbol)
            .bind(position.quantity)
            .bind(position.avg_price)
            .bind(position.unrealized_pnl)
            .bind(position.realized_pnl)
            .bind(position.created_at)
            .bind(position.updated_at)
            .bind(position.current_price)
            .bind(position.notional_value)
            .bind(position.margin_requirement)
            .execute(&mut *tx)
            .await?;

            position
        };

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

    async fn update_market_price(&self, symbol: &str, current_price: Decimal) -> Result<()> {
        sqlx::query(
            r"
            UPDATE positions SET 
                current_price = $1,
                unrealized_pnl = CASE 
                    WHEN quantity > 0 THEN quantity * ($1 - avg_price)
                    WHEN quantity < 0 THEN quantity * (avg_price - $1)
                    ELSE 0
                END,
                updated_at = $2
            WHERE symbol = $3
            ",
        )
        .bind(current_price)
        .bind(Utc::now())
        .bind(symbol)
        .execute(&self.pool)
        .await?;

        Ok(())
    }

    async fn batch_update_prices(
        &self,
        price_updates: &[(String, Decimal)],
    ) -> Result<Vec<Position>> {
        if price_updates.is_empty() {
            return Ok(Vec::new());
        }

        let mut tx = self.pool.begin().await?;
        let mut updated_positions = Vec::new();

        for (symbol, price) in price_updates {
            // Update the position
            sqlx::query(
                r"
                UPDATE positions SET 
                    current_price = $1,
                    unrealized_pnl = CASE 
                        WHEN quantity > 0 THEN quantity * ($1 - avg_price)
                        WHEN quantity < 0 THEN quantity * (avg_price - $1)
                        ELSE 0
                    END,
                    updated_at = $2
                WHERE symbol = $3
                ",
            )
            .bind(price)
            .bind(Utc::now())
            .bind(symbol)
            .execute(&mut *tx)
            .await?;

            // Fetch the updated position
            if let Some(position) = sqlx::query_as::<_, Position>(
                r"
                SELECT id, symbol, quantity, avg_price, unrealized_pnl, realized_pnl,
                       created_at, updated_at, current_price, notional_value, margin_requirement
                FROM positions WHERE symbol = $1
                ",
            )
            .bind(symbol)
            .fetch_optional(&mut *tx)
            .await?
            {
                updated_positions.push(position);
            }
        }

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

    async fn close_position(&self, symbol: &str, closing_price: Decimal) -> Result<Position> {
        let mut tx = self.pool.begin().await?;

        // Get the current position
        let mut position = sqlx::query_as::<_, Position>(
            r"
            SELECT id, symbol, quantity, avg_price, unrealized_pnl, realized_pnl,
                   created_at, updated_at, current_price, notional_value, margin_requirement
            FROM positions WHERE symbol = $1 FOR UPDATE
            ",
        )
        .bind(symbol)
        .fetch_optional(&mut *tx)
        .await?
        .ok_or_else(|| {
            RepositoryError::NotFound(format!("Position not found for symbol: {symbol}"))
        })?;

        // Calculate realized P&L
        let realized_pnl_delta = if position.is_long() {
            position.quantity * (closing_price - position.avg_price)
        } else {
            position.quantity * (position.avg_price - closing_price)
        };

        // Update position to closed state
        position.realized_pnl += realized_pnl_delta;
        position.quantity = Decimal::ZERO;
        position.unrealized_pnl = Decimal::ZERO;
        position.current_price = Some(closing_price);
        position.notional_value = Decimal::ZERO;
        position.margin_requirement = Decimal::ZERO;
        position.updated_at = Utc::now();

        sqlx::query(
            r"
            UPDATE positions SET 
                quantity = 0,
                realized_pnl = $1,
                unrealized_pnl = 0,
                current_price = $2,
                notional_value = 0,
                margin_requirement = 0,
                updated_at = $3
            WHERE symbol = $4
            ",
        )
        .bind(position.realized_pnl)
        .bind(closing_price)
        .bind(position.updated_at)
        .bind(symbol)
        .execute(&mut *tx)
        .await?;

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

    async fn get_position_stats(&self) -> Result<PositionStats> {
        let row = sqlx::query(
            r"
            SELECT 
                COUNT(*) as total_positions,
                COUNT(CASE WHEN quantity > 0 THEN 1 END) as long_positions,
                COUNT(CASE WHEN quantity < 0 THEN 1 END) as short_positions,
                COUNT(CASE WHEN quantity = 0 THEN 1 END) as flat_positions,
                COALESCE(SUM(ABS(notional_value)), 0) as total_notional_value,
                COALESCE(SUM(unrealized_pnl), 0) as total_unrealized_pnl,
                COALESCE(SUM(realized_pnl), 0) as total_realized_pnl,
                MAX(ABS(quantity)) as largest_position
            FROM positions
            ",
        )
        .fetch_one(&self.pool)
        .await?;

        // Get most and least profitable symbols
        let profitable_row = sqlx::query(
            r"
            SELECT symbol, (unrealized_pnl + realized_pnl) as total_pnl
            FROM positions 
            WHERE (unrealized_pnl + realized_pnl) != 0
            ORDER BY total_pnl DESC 
            LIMIT 1
            ",
        )
        .fetch_optional(&self.pool)
        .await?;

        let unprofitable_row = sqlx::query(
            r"
            SELECT symbol, (unrealized_pnl + realized_pnl) as total_pnl
            FROM positions 
            WHERE (unrealized_pnl + realized_pnl) != 0
            ORDER BY total_pnl ASC 
            LIMIT 1
            ",
        )
        .fetch_optional(&self.pool)
        .await?;

        Ok(PositionStats {
            total_positions: row.get("total_positions"),
            long_positions: row.get("long_positions"),
            short_positions: row.get("short_positions"),
            flat_positions: row.get("flat_positions"),
            total_notional_value: row.get("total_notional_value"),
            total_unrealized_pnl: row.get("total_unrealized_pnl"),
            total_realized_pnl: row.get("total_realized_pnl"),
            largest_position: row.get("largest_position"),
            most_profitable_symbol: profitable_row.map(|r| r.get("symbol")),
            least_profitable_symbol: unprofitable_row.map(|r| r.get("symbol")),
        })
    }

    async fn find_high_risk_positions(&self, pnl_threshold: Decimal) -> Result<Vec<Position>> {
        let positions = sqlx::query_as::<_, Position>(
            r"
            SELECT id, symbol, quantity, avg_price, unrealized_pnl, realized_pnl,
                   created_at, updated_at, current_price, notional_value, margin_requirement
            FROM positions 
            WHERE unrealized_pnl <= $1 AND quantity != 0
            ORDER BY unrealized_pnl ASC
            ",
        )
        .bind(pnl_threshold)
        .fetch_all(&self.pool)
        .await?;

        Ok(positions)
    }

    async fn calculate_total_pnl(&self) -> Result<PortfolioPnL> {
        let row = sqlx::query(
            r"
            SELECT 
                COALESCE(SUM(unrealized_pnl), 0) as total_unrealized_pnl,
                COALESCE(SUM(realized_pnl), 0) as total_realized_pnl,
                COALESCE(SUM(ABS(notional_value)), 0) as total_notional_value,
                COALESCE(SUM(margin_requirement), 0) as total_margin_requirement,
                COUNT(CASE WHEN quantity != 0 THEN 1 END) as position_count
            FROM positions
            ",
        )
        .fetch_one(&self.pool)
        .await?;

        let total_unrealized_pnl: Decimal = row.get("total_unrealized_pnl");
        let total_realized_pnl: Decimal = row.get("total_realized_pnl");
        let total_notional_value: Decimal = row.get("total_notional_value");
        let total_margin_requirement: Decimal = row.get("total_margin_requirement");
        let position_count: i64 = row.get("position_count");

        let total_pnl = total_unrealized_pnl + total_realized_pnl;
        let roi_percentage = if total_notional_value.is_zero() {
            Decimal::ZERO
        } else {
            total_pnl / total_notional_value * Decimal::from(100)
        };

        Ok(PortfolioPnL {
            total_unrealized_pnl,
            total_realized_pnl,
            total_pnl,
            total_notional_value,
            total_margin_requirement,
            roi_percentage,
            position_count,
        })
    }

    async fn find_margin_call_positions(&self, margin_threshold: Decimal) -> Result<Vec<Position>> {
        let positions = sqlx::query_as::<_, Position>(
            r"
            SELECT id, symbol, quantity, avg_price, unrealized_pnl, realized_pnl,
                   created_at, updated_at, current_price, notional_value, margin_requirement
            FROM positions 
            WHERE quantity != 0 AND (
                (unrealized_pnl < 0 AND ABS(unrealized_pnl) >= margin_requirement * $1) OR
                (margin_requirement > 0 AND (notional_value + unrealized_pnl) / margin_requirement < $1)
            )
            ORDER BY unrealized_pnl ASC
            "
        )
        .bind(margin_threshold)
        .fetch_all(&self.pool)
        .await?;

        Ok(positions)
    }
}

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

    #[test]
    fn test_position_filter_builder() {
        let filter = PositionFilter::new()
            .symbol("EURUSD")
            .position_type(PositionType::Long)
            .limit(25);

        assert_eq!(filter.symbol.as_ref().unwrap(), "EURUSD");
        assert_eq!(filter.position_type.unwrap(), PositionType::Long);
        assert_eq!(filter.limit.unwrap(), 25);
    }

    #[test]
    fn test_position_type_sql_condition() {
        assert_eq!(
            PostgresPositionRepository::position_type_to_sql_condition(PositionType::Long),
            "quantity > 0"
        );
        assert_eq!(
            PostgresPositionRepository::position_type_to_sql_condition(PositionType::Short),
            "quantity < 0"
        );
        assert_eq!(
            PostgresPositionRepository::position_type_to_sql_condition(PositionType::Flat),
            "quantity = 0"
        );
    }

    #[test]
    fn test_portfolio_pnl() {
        let portfolio_pnl = PortfolioPnL {
            total_unrealized_pnl: dec!(5000),
            total_realized_pnl: dec!(2000),
            total_pnl: dec!(7000),
            total_notional_value: dec!(100000),
            total_margin_requirement: dec!(2000),
            roi_percentage: dec!(7.0),
            position_count: 5,
        };

        assert_eq!(portfolio_pnl.total_pnl, dec!(7000));
        assert_eq!(portfolio_pnl.roi_percentage, dec!(7.0));
        assert_eq!(portfolio_pnl.position_count, 5);
    }
}
