use std::io::{self, Write};

use anyhow::Result;

use super::oauth;
use super::token::{self, Session};
use crate::lexicons::com_atproto_server;
use crate::types::{CreateSessionRequest, CreateSessionResponse};
use crate::xrpc::XrpcClient;

/// Login to ATProto PDS (with 2FA support)
pub async fn login(handle: &str, password: &str, pds: &str, is_bot: bool) -> Result<()> {
    let client = XrpcClient::new(pds);

    let req = CreateSessionRequest {
        identifier: handle.to_string(),
        password: password.to_string(),
        auth_factor_token: None,
    };

    let account_type = if is_bot { "bot" } else { "user" };
    println!("Logging in to {} as {} ({})...", pds, handle, account_type);

    let session_res = match client
        .call_unauth::<_, CreateSessionResponse>(&com_atproto_server::CREATE_SESSION, &req)
        .await
    {
        Ok(res) => res,
        Err(e) => {
            // Check if 2FA is required
            let err_str = e.to_string();
            if err_str.contains("AuthFactorTokenRequired") {
                eprintln!("2FA is enabled. Check your email for a confirmation code.");
                eprint!("Enter 2FA code: ");
                io::stderr().flush()?;
                let mut code = String::new();
                io::stdin().read_line(&mut code)?;
                let code = code.trim().to_string();

                if code.is_empty() {
                    anyhow::bail!("No code entered, aborting.");
                }

                let req_2fa = CreateSessionRequest {
                    identifier: handle.to_string(),
                    password: password.to_string(),
                    auth_factor_token: Some(code),
                };

                client
                    .call_unauth(&com_atproto_server::CREATE_SESSION, &req_2fa)
                    .await?
            } else {
                return Err(e);
            }
        }
    };

    let session = Session {
        did: session_res.did,
        handle: session_res.handle,
        access_jwt: session_res.access_jwt,
        refresh_jwt: session_res.refresh_jwt,
        pds: Some(pds.to_string()),
    };

    if is_bot {
        token::save_bot_session(&session)?;
    } else {
        token::save_session(&session)?;
    }
    eprintln!("Logged in as {} ({})", session.handle, session.did);

    Ok(())
}

/// Refresh a session (shared logic for user and bot, legacy app-password)
async fn do_refresh(session: &Session, pds: &str) -> Result<Session> {
    let client = XrpcClient::new(pds);

    let new_session: CreateSessionResponse = client
        .call_bearer(&com_atproto_server::REFRESH_SESSION, &session.refresh_jwt)
        .await?;

    Ok(Session {
        did: new_session.did,
        handle: new_session.handle,
        access_jwt: new_session.access_jwt,
        refresh_jwt: new_session.refresh_jwt,
        pds: Some(pds.to_string()),
    })
}

/// Check if a JWT access token is still valid (with 60s margin)
fn is_token_valid(token: &str) -> bool {
    let parts: Vec<&str> = token.split('.').collect();
    if parts.len() < 2 {
        return false;
    }
    // Decode JWT payload
    use base64::engine::{general_purpose::URL_SAFE_NO_PAD, Engine};
    let payload = match URL_SAFE_NO_PAD.decode(parts[1]) {
        Ok(p) => p,
        Err(_) => return false,
    };
    let json: serde_json::Value = match serde_json::from_slice(&payload) {
        Ok(v) => v,
        Err(_) => return false,
    };
    let exp = json["exp"].as_i64().unwrap_or(0);
    let now = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs() as i64;
    exp > now + 60 // valid if expires more than 60s from now
}

/// Build a Session from an OAuthSession (without touching token.json)
fn session_from_oauth(oauth: &oauth::OAuthSession) -> Session {
    let pds_host = oauth.pds
        .strip_prefix("https://")
        .unwrap_or(&oauth.pds)
        .trim_end_matches('/');
    Session {
        did: oauth.did.clone(),
        handle: oauth.handle.clone(),
        access_jwt: oauth.access_token.clone(),
        refresh_jwt: String::new(), // not used for OAuth
        pds: Some(pds_host.to_string()),
    }
}

/// Get OAuth session and return it if access_token is still valid.
/// If expired, refresh it. If refresh fails, remove the OAuth session file
/// so we cleanly fall back to legacy on next call.
async fn try_oauth_session(is_bot: bool) -> Option<Session> {
    if !oauth::has_oauth_session(is_bot) {
        return None;
    }
    let oauth_session = match oauth::load_oauth_session(is_bot) {
        Ok(s) => s,
        Err(_) => return None,
    };
    // If token is still valid, use it without refreshing
    if is_token_valid(&oauth_session.access_token) {
        return Some(session_from_oauth(&oauth_session));
    }
    // Token expired, try refresh
    match oauth::refresh_oauth_session(is_bot).await {
        Ok((new_oauth, _session)) => Some(session_from_oauth(&new_oauth)),
        Err(_) => {
            // Refresh failed — remove broken OAuth session so legacy works
            oauth::remove_oauth_session(is_bot);
            None
        }
    }
}

/// Refresh access token (OAuth-aware: uses cached token if still valid)
pub async fn refresh_session() -> Result<Session> {
    if let Some(session) = try_oauth_session(false).await {
        return Ok(session);
    }

    let session = token::load_session()?;
    let pds = session.pds.as_deref().unwrap_or("bsky.social");

    let new_session = do_refresh(&session, pds).await?;
    token::save_session(&new_session)?;

    Ok(new_session)
}

/// Refresh bot access token (OAuth-aware: uses cached token if still valid)
pub async fn refresh_bot_session() -> Result<Session> {
    if let Some(session) = try_oauth_session(true).await {
        return Ok(session);
    }

    let session = token::load_bot_session()?;
    let pds = session.pds.as_deref().unwrap_or("bsky.social");

    let new_session = do_refresh(&session, pds).await?;
    token::save_bot_session(&new_session)?;

    Ok(new_session)
}
