#![allow(missing_docs, clippy::missing_const_for_fn, clippy::future_not_send)]

use core::marker::PhantomData;
use futures::future::{BoxFuture, LocalBoxFuture};
use futures_kind::{FutureKind, IntoFutureKind, Local, Sendable, kinds};

trait Counter<K: FutureKind> {
    fn next(&self) -> K::Future<'_, u32>;
}

struct Memory<K> {
    val: u32,
    _marker: PhantomData<K>,
}

impl<K> Memory<K> {
    fn new(val: u32) -> Self {
        Self {
            val,
            _marker: PhantomData,
        }
    }
}

// Single proc macro generates both Sendable and Local impls
#[kinds]
impl<K: FutureKind> Counter<K> for Memory<K> {
    fn next(&self) -> K::Future<'_, u32> {
        let val = self.val;
        K::into_kind(async move { val + 1 })
    }
}

#[tokio::test]
async fn test_macro_impl_with_sendable() {
    let memory: Memory<Sendable> = Memory::new(41);
    let result = <Memory<Sendable> as Counter<Sendable>>::next(&memory).await;
    assert_eq!(result, 42);
}

#[tokio::test]
async fn test_macro_impl_with_local() {
    let memory: Memory<Local> = Memory::new(41);
    let result = <Memory<Local> as Counter<Local>>::next(&memory).await;
    assert_eq!(result, 42);
}

// Test with multiple methods and arguments
trait Database<K: FutureKind> {
    fn get(&self, key: &str) -> K::Future<'_, Option<String>>;
    fn count(&self) -> K::Future<'_, usize>;
}

struct InMemoryDb<K> {
    data: std::collections::HashMap<String, String>,
    _marker: PhantomData<K>,
}

#[kinds]
impl<K: FutureKind> Database<K> for InMemoryDb<K> {
    fn get(&self, key: &str) -> K::Future<'_, Option<String>> {
        let key = key.to_string();
        K::into_kind(async move { self.data.get(&key).cloned() })
    }

    fn count(&self) -> K::Future<'_, usize> {
        K::into_kind(async move { self.data.len() })
    }
}

#[tokio::test]
async fn test_database_sendable() {
    let mut data = std::collections::HashMap::new();
    data.insert("foo".to_string(), "bar".to_string());
    let db: InMemoryDb<Sendable> = InMemoryDb {
        data,
        _marker: PhantomData,
    };

    assert_eq!(
        <InMemoryDb<Sendable> as Database<Sendable>>::get(&db, "foo").await,
        Some("bar".to_string())
    );
    assert_eq!(
        <InMemoryDb<Sendable> as Database<Sendable>>::get(&db, "missing").await,
        None
    );
    assert_eq!(
        <InMemoryDb<Sendable> as Database<Sendable>>::count(&db).await,
        1
    );
}

#[tokio::test]
async fn test_database_local() {
    let mut data = std::collections::HashMap::new();
    data.insert("foo".to_string(), "bar".to_string());
    let db: InMemoryDb<Local> = InMemoryDb {
        data,
        _marker: PhantomData,
    };

    assert_eq!(
        <InMemoryDb<Local> as Database<Local>>::get(&db, "foo").await,
        Some("bar".to_string())
    );
    assert_eq!(
        <InMemoryDb<Local> as Database<Local>>::get(&db, "missing").await,
        None
    );
    assert_eq!(<InMemoryDb<Local> as Database<Local>>::count(&db).await, 1);
}

// Test selective generation - only Sendable
trait SendableOnly<K: FutureKind> {
    fn send_only(&self) -> K::Future<'_, u32>;
}

struct SendOnlyType {
    val: u32,
}

#[kinds(Sendable)]
impl<K: FutureKind> SendableOnly<K> for SendOnlyType {
    fn send_only(&self) -> K::Future<'_, u32> {
        let val = self.val;
        K::into_kind(async move { val * 2 })
    }
}

#[tokio::test]
async fn test_sendable_only() {
    let t = SendOnlyType { val: 21 };
    let result = <SendOnlyType as SendableOnly<Sendable>>::send_only(&t).await;
    assert_eq!(result, 42);
}

// Test selective generation - only Local
trait LocalOnly<K: FutureKind> {
    fn local_only(&self) -> K::Future<'_, u32>;
}

struct LocalOnlyType {
    val: u32,
}

#[kinds(Local)]
impl<K: FutureKind> LocalOnly<K> for LocalOnlyType {
    fn local_only(&self) -> K::Future<'_, u32> {
        let val = self.val;
        K::into_kind(async move { val * 2 })
    }
}

#[tokio::test]
async fn test_local_only() {
    let t = LocalOnlyType { val: 21 };
    let result = <LocalOnlyType as LocalOnly<Local>>::local_only(&t).await;
    assert_eq!(result, 42);
}

// Test explicit both
trait ExplicitBoth<K: FutureKind> {
    fn explicit(&self) -> K::Future<'_, u32>;
}

struct ExplicitBothType {
    val: u32,
}

#[kinds(Sendable, Local)]
impl<K: FutureKind> ExplicitBoth<K> for ExplicitBothType {
    fn explicit(&self) -> K::Future<'_, u32> {
        let val = self.val;
        K::into_kind(async move { val + 1 })
    }
}

#[tokio::test]
async fn test_explicit_both_sendable() {
    let t = ExplicitBothType { val: 41 };
    let result = <ExplicitBothType as ExplicitBoth<Sendable>>::explicit(&t).await;
    assert_eq!(result, 42);
}

#[tokio::test]
async fn test_explicit_both_local() {
    let t = ExplicitBothType { val: 41 };
    let result = <ExplicitBothType as ExplicitBoth<Local>>::explicit(&t).await;
    assert_eq!(result, 42);
}

// Test trait-in-trait: consuming a FutureKind-generic trait through another generic context
async fn use_counter_generic<K: FutureKind>(counter: &impl Counter<K>) -> u32 {
    counter.next().await
}

#[tokio::test]
async fn test_trait_in_trait_sendable() {
    let memory: Memory<Sendable> = Memory::new(41);
    let result = use_counter_generic::<Sendable>(&memory).await;
    assert_eq!(result, 42);
}

#[tokio::test]
async fn test_trait_in_trait_local() {
    let memory: Memory<Local> = Memory::new(41);
    let result = use_counter_generic::<Local>(&memory).await;
    assert_eq!(result, 42);
}

// Test conditional bounds: T: Send only required for Sendable
trait Processor<K: FutureKind> {
    fn process(&self) -> K::Future<'_, String>;
}

struct Container<T> {
    value: T,
}

#[kinds(Sendable where T: Send, Local)]
impl<K: FutureKind, T: Clone + ToString> Processor<K> for Container<T> {
    fn process(&self) -> K::Future<'_, String> {
        let value = self.value.clone();
        K::into_kind(async move { value.to_string() })
    }
}

#[tokio::test]
async fn test_conditional_bounds_sendable() {
    // String is Send + Clone + ToString
    let container = Container {
        value: "hello".to_string(),
    };
    let result = <Container<String> as Processor<Sendable>>::process(&container).await;
    assert_eq!(result, "hello");
}

#[tokio::test]
async fn test_conditional_bounds_local() {
    // Rc<str> is !Send but Clone + ToString - only works with Local
    use std::rc::Rc;
    let container = Container {
        value: Rc::<str>::from("world"),
    };
    let result = <Container<Rc<str>> as Processor<Local>>::process(&container).await;
    assert_eq!(result, "world");
}

// Test conditional bounds on Local variant
trait Debugger<K: FutureKind> {
    fn debug(&self) -> K::Future<'_, String>;
}

struct Wrapper<T>(T);

// Local requires Debug, Sendable requires Send
#[kinds(Sendable where T: Send, Local where T: core::fmt::Debug)]
impl<K: FutureKind, T: Clone> Debugger<K> for Wrapper<T> {
    fn debug(&self) -> K::Future<'_, String> {
        K::into_kind(async move { "debugged".to_string() })
    }
}

#[tokio::test]
async fn test_local_conditional_bounds() {
    // i32 is Debug + Clone, works with Local
    let wrapper = Wrapper(42i32);
    let result = <Wrapper<i32> as Debugger<Local>>::debug(&wrapper).await;
    assert_eq!(result, "debugged");
}

#[tokio::test]
async fn test_sendable_conditional_bounds_separate() {
    // i32 is Send + Clone, works with Sendable
    let wrapper = Wrapper(42i32);
    let result = <Wrapper<i32> as Debugger<Sendable>>::debug(&wrapper).await;
    assert_eq!(result, "debugged");
}

// ============================================================
// IntoFutureKind and FutureKind::into_kind tests
// ============================================================

// Test IntoFutureKind trait directly
#[tokio::test]
async fn test_into_future_kind_sendable() {
    let future: BoxFuture<'_, u32> = IntoFutureKind::into_kind(async { 42 });
    assert_eq!(future.await, 42);
}

#[tokio::test]
async fn test_into_future_kind_local() {
    let future: LocalBoxFuture<'_, u32> = IntoFutureKind::into_kind(async { 42 });
    assert_eq!(future.await, 42);
}

// Test FutureKind::into_kind method with concrete types
#[tokio::test]
async fn test_sendable_into_kind() {
    let future = Sendable::into_kind(async { 42 });
    assert_eq!(future.await, 42);
}

#[tokio::test]
async fn test_local_into_kind() {
    let future = Local::into_kind(async { 42 });
    assert_eq!(future.await, 42);
}

// Test K::into_kind in a generic context
trait Calculator<K: FutureKind> {
    fn add(&self, a: u32, b: u32) -> K::Future<'_, u32>;
}

struct SimpleCalculator;

impl Calculator<Sendable> for SimpleCalculator {
    fn add(&self, a: u32, b: u32) -> BoxFuture<'_, u32> {
        Sendable::into_kind(async move { a + b })
    }
}

impl Calculator<Local> for SimpleCalculator {
    fn add(&self, a: u32, b: u32) -> LocalBoxFuture<'_, u32> {
        Local::into_kind(async move { a + b })
    }
}

#[tokio::test]
async fn test_into_kind_in_trait_sendable() {
    let calc = SimpleCalculator;
    let result = <SimpleCalculator as Calculator<Sendable>>::add(&calc, 20, 22).await;
    assert_eq!(result, 42);
}

#[tokio::test]
async fn test_into_kind_in_trait_local() {
    let calc = SimpleCalculator;
    let result = <SimpleCalculator as Calculator<Local>>::add(&calc, 20, 22).await;
    assert_eq!(result, 42);
}

// Test that Sendable::into_kind enforces Send bound
// (This is a compile-time check - if it compiles, it works)
#[tokio::test]
async fn test_sendable_into_kind_with_send_data() {
    let data = String::from("hello");
    let future = Sendable::into_kind(async move { if data.is_empty() { 0u32 } else { 42u32 } });
    assert_eq!(future.await, 42);
}

// Test that Local::into_kind works with !Send data
#[tokio::test]
async fn test_local_into_kind_with_non_send_data() {
    use std::rc::Rc;
    let data = Rc::new(42u32);
    let future = Local::into_kind(async move { *data });
    assert_eq!(future.await, 42);
}
