use crate::auth::{AuthToken, AuthResult, AuthError, SecureTokenStorage};
use chrono::{DateTime, Utc, Duration};
use gloo_net::http::Request;
use gloo_timers::callback::Timeout;
use leptos::prelude::*;
use leptos::task::spawn_local;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use wasm_bindgen::prelude::*;

#[derive(Debug, Clone, Serialize, Deserialize)]
struct TokenRefreshResponse {
    access_token: String,
    refresh_token: Option<String>,
    expires_in: i64,
    token_type: String,
}

#[derive(Debug, Clone)]
pub struct TokenManager {
    storage: SecureTokenStorage,
    refresh_timeout: RwSignal<Option<Timeout>>,
    api_base_url: String,
}

impl TokenManager {
    pub fn new(api_base_url: String) -> AuthResult<Self> {
        let storage = SecureTokenStorage::new()?;

        Ok(Self {
            storage,
            refresh_timeout: RwSignal::new(None),
            api_base_url,
        })
    }

    pub fn store_token(&self, token: AuthToken) -> AuthResult<()> {
        // Store the token securely
        self.storage.store_token(&token)?;

        // Schedule automatic refresh
        self.schedule_token_refresh(&token)?;

        Ok(())
    }

    pub fn get_current_token(&self) -> AuthResult<Option<AuthToken>> {
        self.storage.retrieve_token()
    }

    pub fn is_token_valid(&self, token: &AuthToken) -> bool {
        let now = Utc::now();
        token.expires_at > now + Duration::minutes(5) // 5-minute buffer
    }

    pub fn clear_token(&self) -> AuthResult<()> {
        // Clear refresh timeout
        self.refresh_timeout.set(None);

        // Remove token from storage
        self.storage.remove_token()?;

        Ok(())
    }

    fn schedule_token_refresh(&self, token: &AuthToken) -> AuthResult<()> {
        let refresh_time = token.expires_at - Duration::minutes(10); // Refresh 10 minutes before expiry
        let now = Utc::now();

        if refresh_time <= now {
            // Token expires soon, refresh immediately
            self.refresh_token_now(token.clone())?;
            return Ok(());
        }

        let duration_ms = (refresh_time - now).num_milliseconds() as u32;
        let token_clone = token.clone();
        let manager_clone = Arc::new(self.clone());

        let timeout = Timeout::new(duration_ms, move || {
            let manager = Arc::clone(&manager_clone);
            let token = token_clone.clone();

            wasm_bindgen_futures::spawn_local(async move {
                if let Err(e) = manager.refresh_token_now(token).await {
                    web_sys::console::error_1(&format!("Token refresh failed: {}", e).into());
                }
            });
        });

        self.refresh_timeout.set(Some(timeout));

        Ok(())
    }

    async fn refresh_token_now(&self, current_token: AuthToken) -> AuthResult<AuthToken> {
        let refresh_url = format!("{}/auth/refresh", self.api_base_url);

        let response = Request::post(&refresh_url)
            .header("Content-Type", "application/json")
            .header("Authorization", &format!("Bearer {}", current_token.refresh_token))
            .send()
            .await
            .map_err(|e| AuthError {
                message: format!("Token refresh request failed: {}", e),
                code: Some("REFRESH_REQUEST_ERROR".to_string()),
                field: None,
            })?;

        if !response.ok() {
            return Err(AuthError {
                message: format!("Token refresh failed with status: {}", response.status()),
                code: Some("REFRESH_FAILED".to_string()),
                field: None,
            });
        }

        let refresh_data: TokenRefreshResponse = response.json()
            .await
            .map_err(|e| AuthError {
                message: format!("Failed to parse refresh response: {}", e),
                code: Some("REFRESH_PARSE_ERROR".to_string()),
                field: None,
            })?;

        let new_token = AuthToken {
            access_token: refresh_data.access_token,
            refresh_token: refresh_data.refresh_token.unwrap_or(current_token.refresh_token),
            expires_at: Utc::now() + Duration::seconds(refresh_data.expires_in),
            token_type: refresh_data.token_type,
        };

        // Store the new token and schedule next refresh
        self.store_token(new_token.clone())?;

        Ok(new_token)
    }

    pub async fn refresh_if_needed(&self) -> AuthResult<Option<AuthToken>> {
        if let Some(current_token) = self.get_current_token()? {
            if !self.is_token_valid(&current_token) {
                let new_token = self.refresh_token_now(current_token).await?;
                return Ok(Some(new_token));
            }
            return Ok(Some(current_token));
        }
        Ok(None)
    }

    pub fn get_auth_header(&self) -> AuthResult<Option<String>> {
        if let Some(token) = self.get_current_token()? {
            if self.is_token_valid(&token) {
                return Ok(Some(format!("{} {}", token.token_type, token.access_token)));
            }
        }
        Ok(None)
    }
}

impl Clone for TokenManager {
    fn clone(&self) -> Self {
        Self {
            storage: SecureTokenStorage::new().expect("Failed to create storage"),
            refresh_timeout: RwSignal::new(None),
            api_base_url: self.api_base_url.clone(),
        }
    }
}

// Global token manager context
#[derive(Clone)]
pub struct AuthContext {
    pub token_manager: TokenManager,
    pub auth_state: RwSignal<crate::auth::AuthState>,
}

pub fn provide_auth_context(api_base_url: String) -> AuthResult<()> {
    let token_manager = TokenManager::new(api_base_url)?;
    let auth_state = RwSignal::new(crate::auth::AuthState::default());

    // Check for existing token on initialization
    if let Ok(Some(token)) = token_manager.get_current_token() {
        if token_manager.is_token_valid(&token) {
            auth_state.update(|state| {
                state.token = Some(token);
                state.is_authenticated = true;
            });
        } else {
            // Token exists but is invalid, try to refresh
            let token_manager_clone = token_manager.clone();
            wasm_bindgen_futures::spawn_local(async move {
                if let Ok(Some(new_token)) = token_manager_clone.refresh_if_needed().await {
                    auth_state.update(|state| {
                        state.token = Some(new_token);
                        state.is_authenticated = true;
                    });
                } else {
                    // Failed to refresh, clear token
                    let _ = token_manager_clone.clear_token();
                }
            });
        }
    }

    let context = AuthContext {
        token_manager,
        auth_state,
    };

    provide_context(context);
    Ok(())
}

pub fn use_auth_context() -> AuthContext {
    expect_context::<AuthContext>()
}

pub fn use_token_manager() -> TokenManager {
    let context = use_auth_context();
    context.token_manager
}
