use std::sync::Arc;

use axum::{
	extract::{FromRequestParts, Request},
	middleware::Next,
	response::Response,
};

use crate::{
	error::ServiceNotFoundError,
	scope::{Scope, ScopeProvider},
};

pub struct DI<T: Send + Sync + 'static>(Arc<T>);
impl<T: Send + Sync + 'static, S: Send + Sync> FromRequestParts<S> for DI<T> {
	type Rejection = &'static str;

	async fn from_request_parts(
		parts: &mut axum::http::request::Parts,
		_: &S,
	) -> Result<Self, Self::Rejection> {
		let Some(sp) = crate::sp() else {
			return Err("Failed to get service provider in DI extractor");
		};
		let Some(scope) = parts.extensions.get::<Arc<Scope>>() else {
			return Err("Failed to extract scope");
		};
		let Ok(service) = sp.dis(Arc::new(Arc::new(AxumExtensionsScopeProvider(
			scope.clone(),
		)))) else {
			return Err("Failed to get service");
		};
		Ok(DI(service))
	}
}

pub struct DIK<T: Send + Sync + 'static, K: 'static>(Arc<T>, std::marker::PhantomData<K>);
impl<T: Send + Sync + 'static, K: Send + Sync + 'static, S: Send + Sync> FromRequestParts<S>
	for DIK<T, K>
{
	type Rejection = &'static str;

	async fn from_request_parts(
		parts: &mut axum::http::request::Parts,
		_: &S,
	) -> Result<Self, Self::Rejection> {
		let Some(sp) = crate::sp() else {
			return Err("Failed to get service provider in DI extractor");
		};
		let Some(scope) = parts.extensions.get::<Arc<Scope>>() else {
			return Err("Failed to extract scope");
		};
		let Ok(service) = sp.disk::<T, K>(Arc::new(Arc::new(AxumExtensionsScopeProvider(
			scope.clone(),
		)))) else {
			return Err("Failed to get service");
		};
		Ok(DIK(service, std::marker::PhantomData))
	}
}

pub async fn scope_middleware(mut req: Request, next: Next) -> Response {
	req.extensions_mut().insert(Arc::new(Scope::new()));
	next.run(req).await
}

pub struct AxumExtensionsScopeProvider(pub Arc<Scope>);

impl<'a> ScopeProvider for AxumExtensionsScopeProvider {
	fn provide_scope(&self) -> Result<Arc<Scope>, ServiceNotFoundError> {
		Ok(self.0.clone())
	}
}
