/// Tests specifically targeting uncovered code paths in hybrid mode for increased coverage
use anyhow::Result;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::time::{Duration, timeout};

use nntp_proxy::config::Server;
use nntp_proxy::types::{MaxConnections, Port};
use nntp_proxy::{Config, NntpProxy, RoutingMode};

use crate::test_helpers::*;

/// Test that hybrid mode handles long-running sessions and triggers metrics flushing
/// This test sends >100 commands to trigger the `METRICS_FLUSH_INTERVAL` code path
#[tokio::test]
async fn test_hybrid_mode_long_session_metrics_flush() -> Result<()> {
    let (mock_port, _mock) = MockNntpServer::new()
        .with_name("Long Session Server")
        .on_command("GROUP", "211 1000 1 1000 alt.test\r\n")
        .on_command(
            "ARTICLE",
            "220 1 <test@example.com>\r\nSubject: Test\r\n\r\nBody\r\n.\r\n",
        )
        .spawn_on_random_port()
        .await?;

    // Create proxy WITH metrics enabled to test metrics flushing
    let config = Config {
        servers: vec![create_test_server_config(
            "127.0.0.1",
            mock_port,
            "Mock Server",
        )],
        ..Default::default()
    };

    let proxy = NntpProxy::builder(config)
        .with_routing_mode(RoutingMode::Hybrid)
        .build()
        .await?;

    let proxy_port = spawn_test_proxy_on_random_port(proxy, true).await?;
    let proxy_addr = format!("127.0.0.1:{proxy_port}");
    wait_for_server(&proxy_addr, 20).await?;

    let mut client = TcpStream::connect(&proxy_addr).await?;

    // Read greeting
    let mut buffer = [0; 1024];
    let n = client.read(&mut buffer).await?;
    assert!(n > 0);

    // Switch to stateful mode with GROUP
    client.write_all(b"GROUP alt.test\r\n").await?;
    client.flush().await?;
    buffer = [0; 1024];
    let n = client.read(&mut buffer).await?;
    assert!(n > 0);

    // Send 150 commands to trigger METRICS_FLUSH_INTERVAL (which is 100)
    // This exercises the periodic metrics flushing code path
    for i in 1..=150 {
        client.write_all(b"ARTICLE 1\r\n").await?;
        client.flush().await?;

        // Read response - reuse the 1024 buffer
        buffer = [0; 1024];
        let n = timeout(Duration::from_secs(1), client.read(&mut buffer))
            .await
            .map_err(|_| anyhow::anyhow!("Timeout on command {i}"))?
            .map_err(|e| anyhow::anyhow!("Read error on command {i}: {e}"))?;

        assert!(n > 0, "Empty response on command {i}");

        // Every 50 commands, log progress
        if i % 50 == 0 {
            println!("Completed {i} commands");
        }
    }

    // Clean shutdown
    client.write_all(b"QUIT\r\n").await?;

    Ok(())
}

/// Test hybrid mode error handling when command execution fails in stateful mode
/// This exercises the error handling code path that logs warnings and breaks the loop
#[tokio::test]
async fn test_hybrid_mode_command_error_in_stateful_mode() -> Result<()> {
    // Create a mock server that will close connection after GROUP to simulate backend error
    let mock_listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
    let mock_port = mock_listener.local_addr()?.port();
    tokio::spawn(async move {
        loop {
            let (mut stream, _) = mock_listener.accept().await.unwrap();

            // Send greeting
            stream.write_all(b"200 Mock Server\r\n").await.unwrap();

            // Handle negotiation commands (MODE READER, etc.) before the real commands
            let mut buf = [0; 1024];
            let cmd = loop {
                let n = stream.read(&mut buf).await.unwrap();
                if n == 0 {
                    break String::new();
                }
                let cmd = std::str::from_utf8(&buf[..n]).unwrap().to_string();
                if cmd.contains("GROUP") || cmd.contains("ARTICLE") || cmd.contains("QUIT") {
                    break cmd;
                }
                // Respond to negotiation commands (MODE READER, etc.)
                stream.write_all(b"200 OK\r\n").await.unwrap();
            };

            if cmd.contains("GROUP") {
                // Send GROUP response
                stream
                    .write_all(b"211 100 1 100 alt.test\r\n")
                    .await
                    .unwrap();

                // Read next command
                let n = stream.read(&mut buf).await.unwrap();
                let cmd2 = std::str::from_utf8(&buf[..n]).unwrap();

                if cmd2.contains("ARTICLE") {
                    // Simulate backend error by closing connection
                    drop(stream);
                    break;
                }
            }
        }
    });

    wait_for_server(&format!("127.0.0.1:{mock_port}"), 20).await?;

    let server = Server::builder("127.0.0.1", Port::try_new(mock_port).unwrap())
        .name("Mock Server")
        .max_connections(MaxConnections::try_new(5).unwrap())
        .compress(Some(false))
        .build()
        .expect("Valid server config");

    let config = Config {
        servers: vec![server],
        ..Default::default()
    };

    let proxy = NntpProxy::new(config, RoutingMode::Hybrid).await?;

    let proxy_port = spawn_test_proxy_on_random_port(proxy, true).await?;
    let proxy_addr = format!("127.0.0.1:{proxy_port}");
    wait_for_server(&proxy_addr, 20).await?;

    let mut client = TcpStream::connect(&proxy_addr).await?;

    // Read greeting
    let mut buffer = [0; 1024];
    let n = client.read(&mut buffer).await?;
    assert!(n > 0);

    // Switch to stateful mode
    client.write_all(b"GROUP alt.test\r\n").await?;
    client.flush().await?;
    buffer = [0; 1024];
    let n = client.read(&mut buffer).await?;
    assert!(n > 0);

    // This command should fail due to backend closing connection
    client.write_all(b"ARTICLE 1\r\n").await?;
    client.flush().await?;

    // The proxy should handle the error gracefully (may disconnect or return error)
    // Either way, the connection state should be handled correctly
    buffer = [0; 1024];
    let result = timeout(Duration::from_secs(2), client.read(&mut buffer)).await;

    // Test passes if we handle various outcomes when backend fails
    match result {
        Ok(Ok(0)) => println!("Connection closed gracefully"),
        Ok(Ok(_)) => println!("Got response (proxy handled error)"),
        Ok(Err(_)) => println!("Read error (expected when backend dies)"),
        Err(_) => println!("Timeout (acceptable when backend connection lost)"),
    }

    Ok(())
}
