use std::collections::HashMap;

use crate::adapters::{MemoryDatabaseAdapter, UserOps, VerificationOps};
use crate::plugins::{
    AccountManagementPlugin, EmailPasswordPlugin, EmailVerificationPlugin,
    PasswordManagementPlugin, SessionManagementPlugin, UserManagementPlugin,
};
use crate::{AthenaAuth, AuthBuilder, AuthConfig, AuthRequest, AuthVerification, HttpMethod};

fn test_config() -> AuthConfig {
    AuthConfig::new("test-secret-key-that-is-at-least-32-characters-long")
        .base_url("http://localhost:3000")
        .password_min_length(8)
}

async fn create_auth_with_user_management() -> AthenaAuth<MemoryDatabaseAdapter> {
    AuthBuilder::new(test_config())
        .database(MemoryDatabaseAdapter::new())
        .plugin(EmailPasswordPlugin::new().enable_signup(true))
        .plugin(SessionManagementPlugin::new())
        .plugin(PasswordManagementPlugin::new().require_current_password(true))
        .plugin(AccountManagementPlugin::new())
        .plugin(EmailVerificationPlugin::new())
        .plugin(
            UserManagementPlugin::new()
                .change_email_enabled(true)
                .delete_user_enabled(true),
        )
        .build()
        .await
        .expect("failed to create auth with user-management plugin")
}

async fn create_auth_without_user_management() -> AthenaAuth<MemoryDatabaseAdapter> {
    AuthBuilder::new(test_config())
        .database(MemoryDatabaseAdapter::new())
        .plugin(EmailPasswordPlugin::new().enable_signup(true))
        .plugin(SessionManagementPlugin::new())
        .plugin(PasswordManagementPlugin::new().require_current_password(true))
        .plugin(AccountManagementPlugin::new())
        .plugin(EmailVerificationPlugin::new())
        .build()
        .await
        .expect("failed to create auth without user-management plugin")
}

fn post_json(path: &str, body: serde_json::Value) -> AuthRequest {
    let mut req: AuthRequest = AuthRequest::new(HttpMethod::Post, path);
    req.body = Some(body.to_string().into_bytes());
    req.headers
        .insert("content-type".to_string(), "application/json".to_string());
    req.headers
        .insert("origin".to_string(), "http://localhost:3000".to_string());
    req
}

fn post_json_with_auth(path: &str, body: serde_json::Value, token: &str) -> AuthRequest {
    let mut req = post_json(path, body);
    req.headers
        .insert("authorization".to_string(), format!("Bearer {}", token));
    req
}

fn get_with_query(path: &str, query: HashMap<String, String>) -> AuthRequest {
    let mut req = AuthRequest::new(HttpMethod::Get, path);
    req.headers
        .insert("origin".to_string(), "http://localhost:3000".to_string());
    req.query = query;
    req
}

async fn signup_user(auth: &AthenaAuth<MemoryDatabaseAdapter>, email: &str) -> (String, String) {
    let req = post_json(
        "/sign-up/email",
        serde_json::json!({
            "email": email,
            "password": "password123",
            "name": "Collision User",
        }),
    );

    let resp = auth
        .handle_request(req)
        .await
        .expect("signup request failed");
    assert_eq!(resp.status, 200, "signup should succeed");

    let json: serde_json::Value = serde_json::from_slice(&resp.body).expect("valid signup json");
    let token = json["token"]
        .as_str()
        .expect("signup response token string")
        .to_string();
    let user_id = json["user"]["id"]
        .as_str()
        .expect("signup response user.id string")
        .to_string();

    (token, user_id)
}

#[tokio::test]
async fn test_collision_change_email_prefers_plugin_verification_flow() {
    let auth = create_auth_with_user_management().await;
    let (token, user_id) = signup_user(&auth, "collision_change@example.com").await;

    let resp = auth
        .handle_request(post_json_with_auth(
            "/change-email",
            serde_json::json!({ "newEmail": "collision_change_new@example.com" }),
            &token,
        ))
        .await
        .expect("change-email request failed");
    assert_eq!(resp.status, 200);

    let response_json: serde_json::Value =
        serde_json::from_slice(&resp.body).expect("valid change-email response");
    assert_eq!(response_json["status"], true);
    assert!(
        response_json["message"]
            .as_str()
            .expect("status message string")
            .contains("Verification email sent"),
        "expected plugin verification flow response, got: {response_json:?}"
    );

    let unchanged_user = auth
        .database()
        .get_user_by_id(&user_id)
        .await
        .expect("db lookup should succeed")
        .expect("user should still exist");
    assert_eq!(
        unchanged_user.email.as_deref(),
        Some("collision_change@example.com"),
        "email should not update until verification completes"
    );

    let identifier = format!("change_email:{}:collision_change_new@example.com", user_id);
    let verification = auth
        .database()
        .get_verification_by_identifier(&identifier)
        .await
        .expect("verification lookup should succeed");
    assert!(
        verification.is_some(),
        "verification token should be created by plugin flow"
    );
}

#[tokio::test]
async fn test_collision_change_email_verify_updates_email() {
    let auth = create_auth_with_user_management().await;
    let (token, user_id) = signup_user(&auth, "collision_verify@example.com").await;

    auth.handle_request(post_json_with_auth(
        "/change-email",
        serde_json::json!({ "newEmail": "collision_verify_new@example.com" }),
        &token,
    ))
    .await
    .expect("change-email request failed");

    let identifier = format!("change_email:{}:collision_verify_new@example.com", user_id);
    let verification = auth
        .database()
        .get_verification_by_identifier(&identifier)
        .await
        .expect("verification lookup should succeed")
        .expect("verification token should exist");

    let mut query = HashMap::new();
    query.insert("token".to_string(), verification.value().to_string());

    let verify_resp = auth
        .handle_request(get_with_query("/change-email/verify", query))
        .await
        .expect("change-email verify request failed");
    assert_eq!(verify_resp.status, 200);

    let updated_user = auth
        .database()
        .get_user_by_id(&user_id)
        .await
        .expect("db lookup should succeed")
        .expect("user should exist");
    assert_eq!(
        updated_user.email.as_deref(),
        Some("collision_verify_new@example.com")
    );
    assert!(updated_user.email_verified);
}

#[tokio::test]
async fn test_collision_delete_user_prefers_plugin_verification_flow() {
    let auth = create_auth_with_user_management().await;
    let (token, user_id) = signup_user(&auth, "collision_delete@example.com").await;

    let resp = auth
        .handle_request(post_json_with_auth(
            "/delete-user",
            serde_json::json!({}),
            &token,
        ))
        .await
        .expect("delete-user request failed");
    assert_eq!(resp.status, 200);

    let response_json: serde_json::Value =
        serde_json::from_slice(&resp.body).expect("valid delete-user response");
    assert!(
        response_json["message"]
            .as_str()
            .expect("delete-user message string")
            .contains("Verification email sent"),
        "expected plugin verification-gated delete response, got: {response_json:?}"
    );

    let user_after_request = auth
        .database()
        .get_user_by_id(&user_id)
        .await
        .expect("db lookup should succeed");
    assert!(
        user_after_request.is_some(),
        "user should still exist until delete verification"
    );

    let identifier = format!("delete_user:{}", user_id);
    let verification = auth
        .database()
        .get_verification_by_identifier(&identifier)
        .await
        .expect("verification lookup should succeed");
    assert!(
        verification.is_some(),
        "delete verification token should be created by plugin flow"
    );
}

#[tokio::test]
async fn test_no_collision_core_change_email_fallback_without_plugin_route() {
    let auth = create_auth_without_user_management().await;
    let (token, user_id) = signup_user(&auth, "core_fallback@example.com").await;

    let resp = auth
        .handle_request(post_json_with_auth(
            "/change-email",
            serde_json::json!({ "newEmail": "core_fallback_new@example.com" }),
            &token,
        ))
        .await
        .expect("change-email request failed");
    assert_eq!(resp.status, 200);

    let response_json: serde_json::Value =
        serde_json::from_slice(&resp.body).expect("valid core change-email response");
    assert_eq!(response_json["status"], true);
    assert!(
        response_json["message"]
            .as_str()
            .expect("status message string")
            .contains("Email updated"),
        "expected core immediate update response, got: {response_json:?}"
    );

    let updated_user = auth
        .database()
        .get_user_by_id(&user_id)
        .await
        .expect("db lookup should succeed")
        .expect("user should exist");
    assert_eq!(
        updated_user.email.as_deref(),
        Some("core_fallback_new@example.com")
    );
    assert!(
        !updated_user.email_verified,
        "core immediate flow should set email_verified=false"
    );

    let identifier = format!("change_email:{}:core_fallback_new@example.com", user_id);
    let verification = auth
        .database()
        .get_verification_by_identifier(&identifier)
        .await
        .expect("verification lookup should succeed");
    assert!(
        verification.is_none(),
        "core fallback should not create plugin verification token"
    );
}
