#![allow(
    clippy::tests_outside_test_module,
    clippy::unwrap_used,
    clippy::expect_used,
    clippy::indexing_slicing,
    clippy::str_to_string,
    clippy::string_to_string,
    clippy::useless_format,
    clippy::items_after_test_module,
    clippy::too_many_arguments,
    clippy::doc_markdown,
    clippy::shadow_unrelated,
    clippy::use_debug,
    clippy::needless_as_bytes,
    clippy::missing_const_for_fn,
    unused_imports,
    unused_variables,
    unused_mut,
    dead_code,
)]
//! Mock FIX Server for Broker Gateway Testing
//!
//! Provides a lightweight FIX protocol server for integration testing
//! without requiring external CQG connectivity.
//!
//! Features:
//! - Session management (Logon/Logout)
//! - Order acceptance (NewOrderSingle → ExecutionReport)
//! - Latency simulation (configurable 10-20ms delays)
//! - Error injection (rejections, disconnects, sequence gaps)
//! - Message recording for assertion

use anyhow::{Context, Result};
use std::collections::VecDeque;
use std::sync::Arc;
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::sync::RwLock;
use tokio::time::sleep;

/// FIX message representation (simplified for testing)
#[derive(Debug, Clone)]
pub struct FIXMessage {
    pub msg_type: String,        // Tag 35 (e.g., "A" = Logon, "D" = NewOrderSingle)
    pub seq_num: u64,             // Tag 34
    pub sender_comp_id: String,   // Tag 49
    pub target_comp_id: String,   // Tag 56
    pub fields: Vec<(u16, String)>, // All FIX tags
    pub raw: String,              // Raw FIX message
}

impl FIXMessage {
    /// Parse FIX message from raw string
    pub fn parse(raw: &str) -> Result<Self> {
        let mut fields = Vec::new();
        let mut msg_type = String::new();
        let mut seq_num = 0u64;
        let mut sender_comp_id = String::new();
        let mut target_comp_id = String::new();

        for field in raw.split('|') {
            if field.is_empty() {
                continue;
            }

            let parts: Vec<&str> = field.splitn(2, '=').collect();
            if parts.len() != 2 {
                continue;
            }

            let tag: u16 = parts[0].parse().context("Invalid tag")?;
            let value = parts[1].to_string();

            match tag {
                35 => msg_type = value.clone(),
                34 => seq_num = value.parse().context("Invalid seq_num")?,
                49 => sender_comp_id = value.clone(),
                56 => target_comp_id = value.clone(),
                _ => {}
            }

            fields.push((tag, value));
        }

        Ok(Self {
            msg_type,
            seq_num,
            sender_comp_id,
            target_comp_id,
            fields,
            raw: raw.to_string(),
        })
    }

    /// Get field value by tag
    pub fn get_field(&self, tag: u16) -> Option<&str> {
        self.fields
            .iter()
            .find(|(t, _)| *t == tag)
            .map(|(_, v)| v.as_str())
    }

    /// Create Logon response
    pub fn create_logon_response(client_msg: &FIXMessage, seq_num: u64) -> String {
        format!(
            "8=FIX.4.2|9=100|35=A|34={}|49={}|56={}|98=0|108=30|10=123|",
            seq_num,
            client_msg.target_comp_id,
            client_msg.sender_comp_id
        )
    }

    /// Create ExecutionReport (New acknowledgment)
    pub fn create_execution_report_new(
        client_order_id: &str,
        symbol: &str,
        side: &str,
        quantity: &str,
        seq_num: u64,
        sender: &str,
        target: &str,
    ) -> String {
        format!(
            "8=FIX.4.2|9=200|35=8|34={}|49={}|56={}|\
             37=BROKER_{}|11={}|17=EXEC_{}|20=0|150=0|39=0|\
             55={}|54={}|38={}|32=0|31=0|151={}|14=0|6=0|10=145|",
            seq_num,
            sender,
            target,
            &client_order_id[..8.min(client_order_id.len())], // BrokerOrderID
            client_order_id,
            &client_order_id[..8.min(client_order_id.len())], // ExecID
            symbol,
            side,
            quantity,
            quantity // LeavesQty
        )
    }

    /// Create ExecutionReport (Fill)
    pub fn create_execution_report_fill(
        client_order_id: &str,
        broker_order_id: &str,
        symbol: &str,
        side: &str,
        quantity: &str,
        price: &str,
        seq_num: u64,
        sender: &str,
        target: &str,
    ) -> String {
        format!(
            "8=FIX.4.2|9=250|35=8|34={}|49={}|56={}|\
             37={}|11={}|17=EXEC_FILL_{}|20=0|150=F|39=2|\
             55={}|54={}|38={}|32={}|31={}|151=0|14={}|6={}|10=234|",
            seq_num,
            sender,
            target,
            broker_order_id,
            client_order_id,
            &client_order_id[..8.min(client_order_id.len())],
            symbol,
            side,
            quantity,
            quantity, // LastQty
            price,
            quantity, // CumQty
            price     // AvgPx
        )
    }

    /// Create ExecutionReport (Reject)
    pub fn create_execution_report_reject(
        client_order_id: &str,
        symbol: &str,
        reason: &str,
        seq_num: u64,
        sender: &str,
        target: &str,
    ) -> String {
        format!(
            "8=FIX.4.2|9=180|35=8|34={}|49={}|56={}|\
             37=BROKER_REJ|11={}|17=EXEC_REJ_{}|20=0|150=8|39=8|\
             55={}|58={}|10=089|",
            seq_num,
            sender,
            target,
            client_order_id,
            &client_order_id[..8.min(client_order_id.len())],
            symbol,
            reason
        )
    }

    /// Create Heartbeat
    pub fn create_heartbeat(seq_num: u64, sender: &str, target: &str) -> String {
        format!(
            "8=FIX.4.2|9=60|35=0|34={}|49={}|56={}|10=089|",
            seq_num, sender, target
        )
    }
}

/// Session state for mock server
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SessionState {
    Disconnected,
    Connected,
    LoggedIn,
    LoggedOut,
}

/// Mock FIX server for testing
pub struct MockFIXServer {
    listener: Arc<RwLock<Option<TcpListener>>>,
    session_state: Arc<RwLock<SessionState>>,
    received_messages: Arc<RwLock<Vec<FIXMessage>>>,
    response_queue: Arc<RwLock<VecDeque<String>>>,
    seq_num: Arc<RwLock<u64>>,
    latency_ms: Arc<RwLock<u64>>,
    reject_next_order: Arc<RwLock<Option<String>>>, // Reject reason
    pub port: u16,
    pub sender_comp_id: String,
    pub target_comp_id: String,
}

impl MockFIXServer {
    /// Create and start mock FIX server on random port
    pub async fn start() -> Result<Self> {
        let listener = TcpListener::bind("127.0.0.1:0")
            .await
            .context("Failed to bind mock FIX server")?;
        let port = listener.local_addr()?.port();

        let server = Self {
            listener: Arc::new(RwLock::new(Some(listener))),
            session_state: Arc::new(RwLock::new(SessionState::Disconnected)),
            received_messages: Arc::new(RwLock::new(Vec::new())),
            response_queue: Arc::new(RwLock::new(VecDeque::new())),
            seq_num: Arc::new(RwLock::new(1)),
            latency_ms: Arc::new(RwLock::new(0)),
            reject_next_order: Arc::new(RwLock::new(None)),
            port,
            sender_comp_id: "MOCK_CQG".to_string(),
            target_comp_id: "FOXHUNT_CLIENT".to_string(),
        };

        Ok(server)
    }

    /// Accept incoming connection and handle FIX messages
    pub async fn accept_connection(&self) -> Result<()> {
        let listener_guard = self.listener.read().await;
        let listener = listener_guard
            .as_ref()
            .context("Listener not available")?;

        let (mut stream, _) = listener
            .accept()
            .await
            .context("Failed to accept connection")?;

        *self.session_state.write().await = SessionState::Connected;

        // Spawn handler task
        let session_state = self.session_state.clone();
        let received_messages = self.received_messages.clone();
        let seq_num = self.seq_num.clone();
        let latency_ms = self.latency_ms.clone();
        let reject_next_order = self.reject_next_order.clone();
        let sender = self.sender_comp_id.clone();
        let target = self.target_comp_id.clone();

        tokio::spawn(async move {
            let mut buf = vec![0u8; 4096];
            loop {
                match stream.read(&mut buf).await {
                    Ok(0) => break, // Connection closed
                    Ok(n) => {
                        let raw = String::from_utf8_lossy(&buf[..n]).to_string();

                        // Parse FIX message
                        if let Ok(msg) = FIXMessage::parse(&raw) {
                            received_messages.write().await.push(msg.clone());

                            // Simulate latency
                            let latency = *latency_ms.read().await;
                            if latency > 0 {
                                sleep(Duration::from_millis(latency)).await;
                            }

                            // Handle message based on type
                            let response = match msg.msg_type.as_str() {
                                "A" => {
                                    // Logon
                                    *session_state.write().await = SessionState::LoggedIn;
                                    let seq = *seq_num.read().await;
                                    *seq_num.write().await += 1;
                                    Some(FIXMessage::create_logon_response(&msg, seq))
                                }
                                "D" => {
                                    // NewOrderSingle
                                    let seq = *seq_num.read().await;
                                    *seq_num.write().await += 1;

                                    // Check if rejection is configured
                                    let reject_reason =
                                        reject_next_order.write().await.take();

                                    if let Some(reason) = reject_reason {
                                        // Send reject
                                        Some(FIXMessage::create_execution_report_reject(
                                            msg.get_field(11).unwrap_or("UNKNOWN"),
                                            msg.get_field(55).unwrap_or("UNKNOWN"),
                                            &reason,
                                            seq,
                                            &sender,
                                            &target,
                                        ))
                                    } else {
                                        // Send ExecutionReport (New)
                                        Some(FIXMessage::create_execution_report_new(
                                            msg.get_field(11).unwrap_or("UNKNOWN"),
                                            msg.get_field(55).unwrap_or("UNKNOWN"),
                                            msg.get_field(54).unwrap_or("1"),
                                            msg.get_field(38).unwrap_or("0"),
                                            seq,
                                            &sender,
                                            &target,
                                        ))
                                    }
                                }
                                "0" => {
                                    // Heartbeat - respond with heartbeat
                                    let seq = *seq_num.read().await;
                                    *seq_num.write().await += 1;
                                    Some(FIXMessage::create_heartbeat(seq, &sender, &target))
                                }
                                "5" => {
                                    // Logout
                                    *session_state.write().await = SessionState::LoggedOut;
                                    None
                                }
                                _ => None,
                            };

                            if let Some(resp) = response {
                                if stream.write_all(resp.as_bytes()).await.is_err() {
                                    break;
                                }
                            }
                        }
                    }
                    Err(_) => break,
                }
            }

            *session_state.write().await = SessionState::Disconnected;
        });

        Ok(())
    }

    /// Send fill execution report
    pub async fn send_fill(
        &self,
        client_order_id: &str,
        broker_order_id: &str,
        symbol: &str,
        side: &str,
        quantity: &str,
        price: &str,
    ) {
        let seq = *self.seq_num.read().await;
        *self.seq_num.write().await += 1;

        let msg = FIXMessage::create_execution_report_fill(
            client_order_id,
            broker_order_id,
            symbol,
            side,
            quantity,
            price,
            seq,
            &self.sender_comp_id,
            &self.target_comp_id,
        );

        self.response_queue.write().await.push_back(msg);
    }

    /// Set latency simulation (milliseconds)
    pub async fn set_latency(&self, latency_ms: u64) {
        *self.latency_ms.write().await = latency_ms;
    }

    /// Simulate order rejection
    pub async fn reject_next_order(&self, reason: String) {
        *self.reject_next_order.write().await = Some(reason);
    }

    /// Get session state
    pub async fn get_session_state(&self) -> SessionState {
        *self.session_state.read().await
    }

    /// Get all received messages
    pub async fn get_received_messages(&self) -> Vec<FIXMessage> {
        self.received_messages.read().await.clone()
    }

    /// Clear received messages
    pub async fn clear_received_messages(&self) {
        self.received_messages.write().await.clear();
    }

    /// Disconnect (close listener)
    pub async fn disconnect(&self) {
        *self.listener.write().await = None;
        *self.session_state.write().await = SessionState::Disconnected;
    }
}

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

    #[test]
    fn test_fix_message_parse_logon() {
        let raw = "8=FIX.4.2|9=120|35=A|34=1|49=FOXHUNT_CLIENT|56=CQG|98=0|108=30|10=123|";
        let msg = FIXMessage::parse(raw).unwrap();

        assert_eq!(msg.msg_type, "A");
        assert_eq!(msg.seq_num, 1);
        assert_eq!(msg.sender_comp_id, "FOXHUNT_CLIENT");
        assert_eq!(msg.target_comp_id, "CQG");
        assert_eq!(msg.get_field(98), Some("0"));
    }

    #[test]
    fn test_fix_message_parse_new_order_single() {
        let raw = "8=FIX.4.2|9=180|35=D|34=2|49=CLIENT|56=CQG|11=ORDER123|55=ES|54=1|38=10|10=234|";
        let msg = FIXMessage::parse(raw).unwrap();

        assert_eq!(msg.msg_type, "D");
        assert_eq!(msg.seq_num, 2);
        assert_eq!(msg.get_field(11), Some("ORDER123")); // ClOrdID
        assert_eq!(msg.get_field(55), Some("ES")); // Symbol
        assert_eq!(msg.get_field(54), Some("1")); // Side (Buy)
        assert_eq!(msg.get_field(38), Some("10")); // Quantity
    }

    #[test]
    fn test_create_logon_response() {
        let client_msg = FIXMessage {
            msg_type: "A".to_string(),
            seq_num: 1,
            sender_comp_id: "FOXHUNT_CLIENT".to_string(),
            target_comp_id: "CQG".to_string(),
            fields: vec![],
            raw: String::new(),
        };

        let response = FIXMessage::create_logon_response(&client_msg, 1);
        assert!(response.contains("35=A")); // Logon
        assert!(response.contains("49=CQG")); // Sender swapped
        assert!(response.contains("56=FOXHUNT_CLIENT")); // Target swapped
    }

    #[test]
    fn test_create_execution_report_new() {
        let msg = FIXMessage::create_execution_report_new(
            "ORDER123",
            "ES",
            "1",
            "10",
            5,
            "CQG",
            "FOXHUNT_CLIENT",
        );

        assert!(msg.contains("35=8")); // ExecutionReport
        assert!(msg.contains("11=ORDER123")); // ClOrdID
        assert!(msg.contains("55=ES")); // Symbol
        assert!(msg.contains("150=0")); // ExecType=New
        assert!(msg.contains("39=0")); // OrdStatus=New
    }

    #[test]
    fn test_create_execution_report_fill() {
        let msg = FIXMessage::create_execution_report_fill(
            "ORDER123",
            "BROKER456",
            "ES",
            "1",
            "10",
            "5800.25",
            10,
            "CQG",
            "FOXHUNT_CLIENT",
        );

        assert!(msg.contains("35=8")); // ExecutionReport
        assert!(msg.contains("11=ORDER123")); // ClOrdID
        assert!(msg.contains("37=BROKER456")); // OrderID
        assert!(msg.contains("150=F")); // ExecType=Fill
        assert!(msg.contains("39=2")); // OrdStatus=Filled
        assert!(msg.contains("31=5800.25")); // LastPx
    }

    #[tokio::test]
    async fn test_mock_server_accept_logon() {
        let server = MockFIXServer::start().await.unwrap();

        // Spawn accept task
        let server_clone = server.clone();
        tokio::spawn(async move {
            server_clone.accept_connection().await.ok();
        });

        // Give server time to start
        tokio::time::sleep(Duration::from_millis(50)).await;

        // Connect as client
        let mut stream = TcpStream::connect(format!("127.0.0.1:{}", server.port))
            .await
            .unwrap();

        // Send Logon
        let logon = "8=FIX.4.2|9=120|35=A|34=1|49=FOXHUNT_CLIENT|56=CQG|98=0|108=30|10=123|";
        stream.write_all(logon.as_bytes()).await.unwrap();

        // Read response
        let mut buf = vec![0u8; 1024];
        tokio::time::sleep(Duration::from_millis(100)).await;
        let n = stream.read(&mut buf).await.unwrap();
        let response = String::from_utf8_lossy(&buf[..n]);

        assert!(response.contains("35=A")); // Logon response
        assert_eq!(server.get_session_state().await, SessionState::LoggedIn);
    }

    #[tokio::test]
    async fn test_mock_server_latency_simulation() {
        let server = MockFIXServer::start().await.unwrap();
        server.set_latency(50).await; // 50ms latency

        let server_clone = server.clone();
        tokio::spawn(async move {
            server_clone.accept_connection().await.ok();
        });

        tokio::time::sleep(Duration::from_millis(50)).await;

        let mut stream = TcpStream::connect(format!("127.0.0.1:{}", server.port))
            .await
            .unwrap();

        let logon = "8=FIX.4.2|9=120|35=A|34=1|49=FOXHUNT_CLIENT|56=CQG|98=0|108=30|10=123|";

        let start = std::time::Instant::now();
        stream.write_all(logon.as_bytes()).await.unwrap();

        let mut buf = vec![0u8; 1024];
        let _ = stream.read(&mut buf).await.unwrap();
        let latency = start.elapsed();

        // Should be at least 50ms due to latency simulation
        assert!(latency >= Duration::from_millis(45)); // Allow 5ms tolerance
    }

    #[tokio::test]
    async fn test_mock_server_reject_order() {
        let server = MockFIXServer::start().await.unwrap();
        server
            .reject_next_order("Insufficient margin".to_string())
            .await;

        let server_clone = server.clone();
        tokio::spawn(async move {
            server_clone.accept_connection().await.ok();
        });

        tokio::time::sleep(Duration::from_millis(50)).await;

        let mut stream = TcpStream::connect(format!("127.0.0.1:{}", server.port))
            .await
            .unwrap();

        // Send NewOrderSingle
        let order = "8=FIX.4.2|9=180|35=D|34=2|49=CLIENT|56=CQG|11=ORDER123|55=ES|54=1|38=10|10=234|";
        stream.write_all(order.as_bytes()).await.unwrap();

        let mut buf = vec![0u8; 1024];
        tokio::time::sleep(Duration::from_millis(100)).await;
        let n = stream.read(&mut buf).await.unwrap();
        let response = String::from_utf8_lossy(&buf[..n]);

        assert!(response.contains("150=8")); // ExecType=Rejected
        assert!(response.contains("39=8")); // OrdStatus=Rejected
        assert!(response.contains("Insufficient margin"));
    }

    #[tokio::test]
    async fn test_mock_server_get_received_messages() {
        let server = MockFIXServer::start().await.unwrap();

        let server_clone = server.clone();
        tokio::spawn(async move {
            server_clone.accept_connection().await.ok();
        });

        tokio::time::sleep(Duration::from_millis(50)).await;

        let mut stream = TcpStream::connect(format!("127.0.0.1:{}", server.port))
            .await
            .unwrap();

        // Send Logon + Order
        let logon = "8=FIX.4.2|9=120|35=A|34=1|49=CLIENT|56=CQG|98=0|108=30|10=123|";
        stream.write_all(logon.as_bytes()).await.unwrap();
        tokio::time::sleep(Duration::from_millis(100)).await;

        let order = "8=FIX.4.2|9=180|35=D|34=2|49=CLIENT|56=CQG|11=ORDER123|55=ES|54=1|38=10|10=234|";
        stream.write_all(order.as_bytes()).await.unwrap();
        tokio::time::sleep(Duration::from_millis(100)).await;

        let messages = server.get_received_messages().await;
        assert_eq!(messages.len(), 2);
        assert_eq!(messages[0].msg_type, "A"); // Logon
        assert_eq!(messages[1].msg_type, "D"); // NewOrderSingle
    }
}

// Clone implementation for Arc-wrapped server
impl Clone for MockFIXServer {
    fn clone(&self) -> Self {
        Self {
            listener: self.listener.clone(),
            session_state: self.session_state.clone(),
            received_messages: self.received_messages.clone(),
            response_queue: self.response_queue.clone(),
            seq_num: self.seq_num.clone(),
            latency_ms: self.latency_ms.clone(),
            reject_next_order: self.reject_next_order.clone(),
            port: self.port,
            sender_comp_id: self.sender_comp_id.clone(),
            target_comp_id: self.target_comp_id.clone(),
        }
    }
}
