use anyhow::{Context, Result};
use fido_types::User;

use crate::api::{ApiClient, ApiError};
use crate::session::SessionStore;

pub struct RestoredSession {
    pub user: User,
    pub session_token: String,
    pub api_client: ApiClient,
}

pub async fn restore_existing_session(
    mut api_client: ApiClient,
) -> Result<Option<RestoredSession>> {
    let session_store = SessionStore::for_server(api_client.base_url())
        .context("Failed to initialize server-scoped session store")?;

    // Try to load session token from file
    let token = match session_store.load()? {
        Some(t) => t,
        None => {
            log::debug!("No existing session found");
            return Ok(None);
        }
    };

    log::info!("Found existing session token, validating with server");

    // Set the session token in the API client
    api_client.set_session_token(Some(token.clone()));

    // Validate the session with the server
    match api_client.validate_session().await {
        Ok(response) if response.valid => {
            log::info!("Session is valid for user: {}", response.user.username);
            Ok(Some(RestoredSession {
                user: response.user,
                session_token: token,
                api_client,
            }))
        }
        Ok(_) => {
            log::warn!("Session validation returned invalid");
            // Clear invalid session
            let _ = session_store.delete();
            Ok(None)
        }
        Err(ApiError::Unauthorized(_))
        | Err(ApiError::NotFound(_))
        | Err(ApiError::BadRequest(_)) => {
            log::warn!("Session token rejected by server, clearing local token");
            let _ = session_store.delete();
            Ok(None)
        }
        Err(e) => {
            // Keep the local token on transient failures (network/server/rate-limit).
            // This prevents unnecessary re-authentication after temporary outages.
            log::warn!(
                "Session validation failed due to transient error; keeping local token: {}",
                e
            );
            Ok(None)
        }
    }
}

/// Manages the OAuth authentication flow for the TUI client.
///
/// This struct handles:
/// - Initiating GitHub OAuth flow
/// - Opening the system browser
/// - Polling for session completion
/// - Storing session tokens
pub struct AuthFlow {
    api_client: ApiClient,
    session_store: SessionStore,
}

impl AuthFlow {
    /// Creates a new AuthFlow instance.
    pub fn new(api_client: ApiClient) -> Result<Self> {
        let session_store = SessionStore::for_server(api_client.base_url())
            .context("Failed to initialize server-scoped session store")?;

        Ok(Self {
            api_client,
            session_store,
        })
    }

    /// Initiates the GitHub Device Flow by requesting a device code from the server.
    ///
    /// # Returns
    ///
    /// Returns a tuple of (device_code, user_code, verification_uri, interval) where:
    /// - `device_code` is used for polling
    /// - `user_code` is displayed to the user
    /// - `verification_uri` is where the user enters the code
    /// - `interval` is how often to poll (in seconds)
    pub async fn initiate_github_device_flow(&self) -> Result<(String, String, String, i64)> {
        log::info!("Initiating GitHub Device Flow");

        let response = self
            .api_client
            .github_device_flow()
            .await
            .map_err(|e| anyhow::anyhow!("Failed to initiate GitHub Device Flow: {}", e))?;

        log::debug!(
            "Received device code with user code: {}",
            response.user_code
        );

        Ok((
            response.device_code,
            response.user_code,
            response.verification_uri,
            response.interval,
        ))
    }

    /// Opens the system browser to the given URL.
    ///
    /// # Arguments
    ///
    /// * `url` - The URL to open in the browser
    ///
    /// # Returns
    ///
    /// Returns an error if the browser cannot be opened.
    pub fn open_browser(&self, url: &str) -> Result<()> {
        log::info!("Opening browser to: {}", url);

        webbrowser::open(url).context("Failed to open browser")?;

        Ok(())
    }

    /// Saves a session token to the session store.
    ///
    /// This is useful when the session token is obtained through other means
    /// (e.g., test user login).
    pub fn save_session(&self, token: &str) -> Result<()> {
        self.session_store
            .save(token)
            .context("Failed to save session token")
    }

    /// Gets a reference to the API client.
    pub fn api_client(&self) -> &ApiClient {
        &self.api_client
    }

    /// Gets a mutable reference to the API client.
    pub fn api_client_mut(&mut self) -> &mut ApiClient {
        &mut self.api_client
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_auth_flow_creation() {
        let api_client = ApiClient::new("http://localhost:3000");
        let auth_flow = AuthFlow::new(api_client);
        assert!(auth_flow.is_ok());
    }
}
