use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;

use monk_sdk::{
    BackpressureStrategy, BufferConfig, ClientConfig, Event, MonkClient, MonkError, RetryConfig,
};

#[tokio::test]
async fn close_prevents_future_buffered_ingest_calls() {
    let client = MonkClient::builder()
        .base_url("https://events.monk.dev")
        .api_key("mk_test_123")
        .build()
        .expect("client should build");

    client.close();

    let err = client
        .ingest_buffered(
            Event::builder()
                .customer_id("cust_123")
                .event_name("api_call")
                .build()
                .expect("event should build"),
        )
        .await
        .expect_err("buffered ingest must fail after close");

    match err {
        MonkError::Validation(msg) => assert!(msg.contains("buffer closed")),
        other => panic!("unexpected error variant: {other}"),
    }
}

#[tokio::test]
async fn from_config_builds_client() {
    let config = ClientConfig {
        base_url: "https://events.monk.dev".to_string(),
        api_key: "mk_test_123".to_string(),
        timeout: Duration::from_secs(5),
        retry: RetryConfig::default(),
        buffer: BufferConfig::default(),
    };

    MonkClient::from_config(config).expect("config-based constructor should build");
}

#[tokio::test]
async fn builder_rejects_invalid_base_url() {
    let result = MonkClient::builder()
        .base_url("not a url")
        .api_key("mk_test_123")
        .build();

    match result {
        Err(MonkError::Validation(msg)) => assert!(msg.contains("base_url is invalid")),
        Err(other) => panic!("unexpected error variant: {other}"),
        Ok(_) => panic!("expected error for invalid base_url"),
    }
}

#[tokio::test]
async fn builder_rejects_missing_api_key() {
    let result = MonkClient::builder()
        .base_url("https://events.monk.dev")
        .build();

    match result {
        Err(MonkError::Validation(msg)) => assert!(msg.contains("api_key is required")),
        Err(other) => panic!("unexpected error variant: {other}"),
        Ok(_) => panic!("expected error for missing api_key"),
    }
}

#[tokio::test]
async fn backpressure_failfast_returns_buffer_full() {
    let client = MonkClient::builder()
        .base_url("https://events.monk.dev")
        .api_key("mk_test_123")
        .buffer(BufferConfig {
            capacity: 2,
            max_batch_size: 100,
            flush_interval: Duration::from_secs(300),
            max_concurrent_flushes: 1,
            backpressure: BackpressureStrategy::FailFast,
        })
        .build()
        .expect("client should build");

    for _ in 0..2 {
        client
            .ingest_buffered(
                Event::builder()
                    .customer_id("cust_123")
                    .event_name("api_call")
                    .build()
                    .expect("event should build"),
            )
            .await
            .expect("should succeed while buffer has capacity");
    }

    let err = client
        .ingest_buffered(
            Event::builder()
                .customer_id("cust_123")
                .event_name("api_call")
                .build()
                .expect("event should build"),
        )
        .await
        .expect_err("should fail when buffer is full");

    match err {
        MonkError::BufferFull { capacity } => assert_eq!(capacity, 2),
        other => panic!("unexpected error variant: {other}"),
    }
}

#[tokio::test]
async fn on_flush_failure_callback_is_invoked() {
    let failure_count = Arc::new(AtomicUsize::new(0));
    let failure_count_clone = Arc::clone(&failure_count);

    let client = MonkClient::builder()
        .base_url("http://127.0.0.1:1")
        .api_key("mk_test_123")
        .buffer(BufferConfig {
            capacity: 100,
            max_batch_size: 5,
            flush_interval: Duration::from_millis(50),
            max_concurrent_flushes: 1,
            backpressure: BackpressureStrategy::FailFast,
        })
        .retry(RetryConfig {
            max_retries: 0,
            deadline: Some(Duration::from_secs(1)),
            ..RetryConfig::default()
        })
        .on_flush_failure(move |count, _err| {
            failure_count_clone.fetch_add(count, Ordering::Relaxed);
        })
        .build()
        .expect("client should build");

    for _ in 0..5 {
        client
            .ingest_buffered(
                Event::builder()
                    .customer_id("cust_123")
                    .event_name("api_call")
                    .build()
                    .expect("event should build"),
            )
            .await
            .expect("buffer send should succeed");
    }

    tokio::time::sleep(Duration::from_secs(3)).await;

    let failed = failure_count.load(Ordering::Relaxed);
    assert!(
        failed > 0,
        "flush failure handler should have been called, got {failed}"
    );
}

#[tokio::test]
async fn idempotency_key_auto_generated() {
    let event = Event::builder()
        .customer_id("cust_123")
        .event_name("api_call")
        .build()
        .expect("event should build");

    assert!(
        event.idempotency_key.is_none(),
        "builder should not set key"
    );
}
