use std::{marker::PhantomData, sync::Arc};

use async_trait::async_trait;
use leptos::prelude::provide_context;
use leptos_actix::{extract, redirect};
use shield::{Session, ShieldDyn, User};
use shield_actix::{ExtractSession, ExtractShield, ExtractUser};
use shield_leptos::{LeptosIntegration, LeptosUser};

pub struct ActixLeptosIntegration<U: User>(PhantomData<U>);

impl<U: User> Default for ActixLeptosIntegration<U> {
    fn default() -> Self {
        Self(Default::default())
    }
}

#[async_trait]
impl<U: User + Clone + 'static> LeptosIntegration for ActixLeptosIntegration<U> {
    async fn extract_shield(&self) -> ShieldDyn {
        let ExtractShield(shield) = extract::<ExtractShield<U>>()
            .await
            .expect("Shield should be extracted");

        ShieldDyn::new(shield)
    }

    async fn extract_session(&self) -> Session {
        let ExtractSession(session) = extract().await.expect("Session should be extracted");

        session
    }

    async fn extract_user(&self) -> Option<LeptosUser> {
        let ExtractUser(user) = extract::<ExtractUser<U>>()
            .await
            .expect("User should be extracted");

        user.map(|user| user.into())
    }

    fn redirect(&self, path: &str) {
        redirect(path);
    }
}

pub fn provide_actix_integration<U: User + Clone + 'static>() {
    provide_context::<Arc<dyn LeptosIntegration>>(Arc::new(ActixLeptosIntegration::<U>::default()));
}
