use std::path::Path;

use anyhow::Result;
use cdk::mint_url::MintUrl;
use cdk::nuts::MintInfo;
use cdk::wallet::WalletRepository;
use cdk::OidcClient;
use clap::Args;
use serde::{Deserialize, Serialize};

use crate::token_storage;

#[derive(Args, Serialize, Deserialize)]
pub struct CatLoginSubCommand {
    /// Mint url
    mint_url: MintUrl,
    /// Username
    username: String,
    /// Password
    password: String,
}

pub async fn cat_login(
    wallet_repository: &WalletRepository,
    sub_command_args: &CatLoginSubCommand,
    work_dir: &Path,
) -> Result<()> {
    let mint_url = sub_command_args.mint_url.clone();

    // Ensure the mint exists
    if !wallet_repository.has_mint(&mint_url).await {
        wallet_repository.add_wallet(mint_url.clone()).await?;
    }

    let mint_info = wallet_repository.fetch_mint_info(&mint_url).await?;

    let (access_token, refresh_token) = get_access_token(
        &mint_info,
        &sub_command_args.username,
        &sub_command_args.password,
    )
    .await;

    // Save tokens to file in work directory
    if let Err(e) =
        token_storage::save_tokens(work_dir, &mint_url, &access_token, &refresh_token).await
    {
        println!("Warning: Failed to save tokens to file: {e}");
    } else {
        println!("Tokens saved to work directory");
    }

    println!("\nAuthentication successful! 🎉\n");
    println!("\nYour tokens:");
    println!("access_token: {access_token}");
    println!("refresh_token: {refresh_token}");

    Ok(())
}

async fn get_access_token(mint_info: &MintInfo, user: &str, password: &str) -> (String, String) {
    let openid_discovery = mint_info
        .nuts
        .nut21
        .clone()
        .expect("Nut21 defined")
        .openid_discovery;

    let client_id = mint_info
        .nuts
        .nut21
        .clone()
        .expect("Nut21 defined")
        .client_id;

    let oidc_client = OidcClient::new(openid_discovery, None);

    // Get the token endpoint from the OIDC configuration
    let token_url = oidc_client
        .get_oidc_config()
        .await
        .expect("Failed to get OIDC config")
        .token_endpoint;

    // Create the request parameters
    let params = [
        ("grant_type", "password"),
        ("client_id", &client_id),
        ("scope", "openid offline_access"),
        ("username", user),
        ("password", password),
    ];

    // Make the token request directly
    let client = cdk_common::HttpClient::new();
    let token_response: serde_json::Value = client
        .post_form(&token_url, &params)
        .await
        .expect("Failed to send token request");

    let access_token = token_response["access_token"]
        .as_str()
        .expect("No access token in response")
        .to_string();

    let refresh_token = token_response["refresh_token"]
        .as_str()
        .expect("No refresh token in response")
        .to_string();

    (access_token, refresh_token)
}
