//! Auth setup, HTTP request builders, and signup/signin helpers.
//!
//! This is the **canonical** location for all shared test utilities.
//! All integration test files should use `use compat::helpers::*;`.

#![allow(dead_code)]

use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};

use athena_auth::{
    AthenaAuth, AuthBuilder, AuthConfig, AuthUser, MemoryDatabaseAdapter, UserOps,
    plugins::{
        AccountManagementPlugin, AdminPlugin, ApiKeyPlugin, EmailPasswordPlugin,
        EmailVerificationPlugin, OAuthPlugin, OrganizationPlugin, PasskeyPlugin,
        PasswordManagementPlugin, SessionManagementPlugin, TwoFactorPlugin, UserManagementPlugin,
    },
    types::{AuthRequest, HttpMethod},
};
use serde_json::Value;

// ---------------------------------------------------------------------------
// Unique email generator
// ---------------------------------------------------------------------------

static EMAIL_COUNTER: AtomicU64 = AtomicU64::new(0);

/// Generate a unique email address for testing, avoiding hard-coded collisions.
pub fn unique_email(prefix: &str) -> String {
    let n = EMAIL_COUNTER.fetch_add(1, Ordering::Relaxed);
    format!("{prefix}_{n}_{}@test.com", std::process::id())
}

pub fn unique_slug(prefix: &str) -> String {
    let n = EMAIL_COUNTER.fetch_add(1, Ordering::Relaxed);
    format!("{prefix}-{n}-{}", std::process::id())
}

// ---------------------------------------------------------------------------
// Auth setup
// ---------------------------------------------------------------------------

/// Generate a deterministic test-only key (not a real secret).
pub fn test_secret() -> String {
    "t]e]s]t]-]o]n]l]y]-]k]e]y]-]n]o]t]-]a]-]r]e]a]l]-]s]e]c]r]e]t]-]3]2]c]h".replace(']', "")
}

pub fn test_config() -> AuthConfig {
    AuthConfig::new(test_secret())
        .base_url("http://localhost:3000")
        .password_min_length(8)
}

pub async fn create_test_auth() -> 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(ApiKeyPlugin::builder().build())
        .plugin(OAuthPlugin::new())
        .plugin(TwoFactorPlugin::new())
        .plugin(OrganizationPlugin::new())
        .plugin(
            PasskeyPlugin::new()
                .rp_id("localhost")
                .rp_name("Athena Auth Test")
                .origin("http://localhost:3000")
                .allow_insecure_unverified_assertion(true),
        )
        .plugin(AdminPlugin::new())
        .build()
        .await
        .expect("Failed to create test auth instance")
}

/// Same plugin stack as the `server` binary with an explicit [`AuthConfig`] (e.g. dynamic `base_url`).
pub async fn create_server_binary_equivalent_memory_auth_with_config(
    config: AuthConfig,
) -> AthenaAuth<MemoryDatabaseAdapter> {
    let passkey_origin = config.base_url.clone();
    AuthBuilder::new(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(ApiKeyPlugin::builder().build())
        .plugin(OAuthPlugin::new())
        .plugin(TwoFactorPlugin::new())
        .plugin(OrganizationPlugin::new())
        .plugin(
            PasskeyPlugin::new()
                .rp_id("localhost")
                .rp_name("Athena Auth Test")
                .origin(passkey_origin)
                .allow_insecure_unverified_assertion(true),
        )
        .plugin(AdminPlugin::new())
        .plugin(
            UserManagementPlugin::new()
                .change_email_enabled(true)
                .delete_user_enabled(true),
        )
        .build()
        .await
        .expect("Failed to create server-equivalent auth instance")
}

/// Same plugin stack as the `server` binary (`src/bin/server.rs`): `create_test_auth` plus
/// [`UserManagementPlugin`] (change-email / delete-user). Use for route-coverage and HTTP parity tests.
pub async fn create_server_binary_equivalent_memory_auth() -> AthenaAuth<MemoryDatabaseAdapter> {
    create_server_binary_equivalent_memory_auth_with_config(test_config()).await
}

/// Server-equivalent stack bound to a concrete HTTP origin (for Axum `127.0.0.1:0` parity tests).
pub async fn create_server_binary_equivalent_memory_auth_with_base_url(
    base_url: &str,
) -> AthenaAuth<MemoryDatabaseAdapter> {
    let config = AuthConfig::new(test_secret())
        .base_url(base_url.to_string())
        .password_min_length(8)
        .trusted_origin(base_url.to_string());
    create_server_binary_equivalent_memory_auth_with_config(config).await
}

// ---------------------------------------------------------------------------
// Request builders
// ---------------------------------------------------------------------------

pub fn post_json(path: &str, body: Value) -> AuthRequest {
    let mut req = 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
}

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

pub fn get_with_auth(path: &str, token: &str) -> AuthRequest {
    let mut req = AuthRequest::new(HttpMethod::Get, path);
    req.headers
        .insert("authorization".to_string(), format!("Bearer {}", token));
    req.headers
        .insert("origin".to_string(), "http://localhost:3000".to_string());
    req
}

pub fn get_with_auth_and_query(path: &str, token: &str, query: Vec<(&str, &str)>) -> AuthRequest {
    let mut req = AuthRequest::new(HttpMethod::Get, path);
    req.headers
        .insert("authorization".to_string(), format!("Bearer {}", token));
    req.headers
        .insert("origin".to_string(), "http://localhost:3000".to_string());
    for (k, v) in query {
        req.query.insert(k.to_string(), v.to_string());
    }
    req
}

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

pub fn delete_with_auth(path: &str, token: &str) -> AuthRequest {
    let mut req = AuthRequest::new(HttpMethod::Delete, path);
    req.headers
        .insert("authorization".to_string(), format!("Bearer {}", token));
    req.headers
        .insert("origin".to_string(), "http://localhost:3000".to_string());
    req
}

/// Build an authenticated POST request with an empty `{}` body (no content-type).
///
/// Matches the pattern used by many integration tests for action endpoints
/// like `/sign-out`, `/revoke-sessions`, `/delete-user`, etc.
pub fn post_with_auth(path: &str, token: &str) -> AuthRequest {
    let mut req = AuthRequest::new(HttpMethod::Post, path);
    req.body = Some(b"{}".to_vec());
    req.headers
        .insert("authorization".to_string(), format!("Bearer {}", token));
    req.headers
        .insert("origin".to_string(), "http://localhost:3000".to_string());
    req
}

// ---------------------------------------------------------------------------
// Send / signup / signin
// ---------------------------------------------------------------------------

pub async fn send_request(
    auth: &AthenaAuth<MemoryDatabaseAdapter>,
    req: AuthRequest,
) -> (u16, Value) {
    let resp = auth
        .handle_request(req)
        .await
        .expect("Request should not panic");
    let status = resp.status;
    let json: Value = serde_json::from_slice(&resp.body)
        .unwrap_or_else(|_| Value::String(String::from_utf8_lossy(&resp.body).to_string()));
    (status, json)
}

pub async fn signup_user(
    auth: &AthenaAuth<MemoryDatabaseAdapter>,
    email: &str,
    password: &str,
    name: &str,
) -> (String, Value) {
    let req = post_json(
        "/sign-up/email",
        serde_json::json!({
            "name": name,
            "email": email,
            "password": password,
        }),
    );
    let (status, json) = send_request(auth, req).await;
    assert_eq!(
        status, 200,
        "signup should succeed, got status {}: {}",
        status, json
    );
    let token = json["token"]
        .as_str()
        .expect("signup response missing token")
        .to_string();
    (token, json)
}

pub async fn signin_user(
    auth: &AthenaAuth<MemoryDatabaseAdapter>,
    email: &str,
    password: &str,
) -> (String, Value) {
    let req = post_json(
        "/sign-in/email",
        serde_json::json!({
            "email": email,
            "password": password,
        }),
    );
    let (status, json) = send_request(auth, req).await;
    assert_eq!(
        status, 200,
        "signin should succeed, got status {}: {}",
        status, json
    );
    let token = json["token"]
        .as_str()
        .expect("signin response missing token")
        .to_string();
    (token, json)
}

// ---------------------------------------------------------------------------
// TestHarness
// ---------------------------------------------------------------------------

/// Unified test harness wrapping `AthenaAuth` with ergonomic helpers.
///
/// # Examples
///
/// ```rust,ignore
/// let h = TestHarness::new().await;
/// let (token, _) = h.signup("alice@test.com", "password123", "Alice").await;
/// let (status, body) = h.send(h.authed_get("/get-session", &token)).await;
/// assert_eq!(status, 200);
/// ```
pub struct TestHarness {
    auth: Arc<AthenaAuth<MemoryDatabaseAdapter>>,
}

#[derive(Debug, Clone)]
pub struct HarnessUser {
    pub user_id: String,
    pub email: String,
    pub password: String,
    pub name: String,
    pub token: String,
}

#[derive(Debug, Clone)]
pub struct HarnessOrganization {
    pub id: String,
    pub name: String,
    pub slug: String,
}

impl TestHarness {
    /// Create a harness with **all** plugins enabled (suitable for compat and
    /// comprehensive integration tests).
    pub async fn new() -> Self {
        let auth = create_test_auth().await;
        Self {
            auth: Arc::new(auth),
        }
    }

    /// Create a harness with a **minimal** plugin set matching
    /// `integration_tests.rs` conventions (EmailPassword, SessionManagement,
    /// PasswordManagement, AccountManagement, ApiKey).
    pub async fn minimal() -> Self {
        let config = AuthConfig::new("test-secret-key-that-is-at-least-32-characters-long")
            .base_url("http://localhost:3000")
            .password_min_length(6);
        let auth = AuthBuilder::new(config)
            .database(MemoryDatabaseAdapter::new())
            .plugin(EmailPasswordPlugin::new().enable_signup(true))
            .plugin(SessionManagementPlugin::new())
            .plugin(PasswordManagementPlugin::new())
            .plugin(AccountManagementPlugin::new())
            .plugin(ApiKeyPlugin::builder().build())
            .build()
            .await
            .expect("Failed to create test auth instance");
        Self {
            auth: Arc::new(auth),
        }
    }

    /// Wrap an existing `Arc<AthenaAuth>` in a harness.
    pub fn from_arc(auth: Arc<AthenaAuth<MemoryDatabaseAdapter>>) -> Self {
        Self { auth }
    }

    /// Access the inner `AthenaAuth` reference.
    pub fn auth(&self) -> &AthenaAuth<MemoryDatabaseAdapter> {
        &self.auth
    }

    /// Consume the harness and return the inner `Arc`.
    pub fn into_arc(self) -> Arc<AthenaAuth<MemoryDatabaseAdapter>> {
        self.auth
    }

    // -------------------------------------------------------------------
    // Request builders (instance methods delegate to helpers that include
    // `origin` header for CSRF by default)
    // -------------------------------------------------------------------

    /// Build a GET request (with `origin` header).
    pub fn get(&self, path: &str) -> AuthRequest {
        get_request(path)
    }

    /// Build an authenticated GET request (with `origin` header).
    pub fn authed_get(&self, path: &str, token: &str) -> AuthRequest {
        get_with_auth(path, token)
    }

    /// Build a POST request with a JSON body (with `origin` header).
    pub fn post(&self, path: &str, body: Value) -> AuthRequest {
        post_json(path, body)
    }

    /// Build an authenticated POST request with a JSON body (with `origin`
    /// header).
    pub fn authed_post(&self, path: &str, body: Value, token: &str) -> AuthRequest {
        post_json_with_auth(path, body, token)
    }

    // -------------------------------------------------------------------
    // Send
    // -------------------------------------------------------------------

    /// Send a request and return `(status_code, parsed_json_body)`.
    pub async fn send(&self, req: AuthRequest) -> (u16, Value) {
        send_request(&self.auth, req).await
    }

    // -------------------------------------------------------------------
    // User lifecycle
    // -------------------------------------------------------------------

    /// Sign up a new user. Returns `(token, response_json)`.
    pub async fn signup(&self, email: &str, password: &str, name: &str) -> (String, Value) {
        let req = self.post(
            "/sign-up/email",
            serde_json::json!({ "name": name, "email": email, "password": password }),
        );
        let (status, json) = self.send(req).await;
        assert_eq!(
            status, 200,
            "signup should succeed, got status {}: {}",
            status, json
        );
        let token = json["token"]
            .as_str()
            .expect("signup response missing token")
            .to_string();
        (token, json)
    }

    /// Sign in an existing user. Returns `(token, response_json)`.
    pub async fn signin(&self, email: &str, password: &str) -> (String, Value) {
        let req = self.post(
            "/sign-in/email",
            serde_json::json!({ "email": email, "password": password }),
        );
        let (status, json) = self.send(req).await;
        assert_eq!(
            status, 200,
            "signin should succeed, got status {}: {}",
            status, json
        );
        let token = json["token"]
            .as_str()
            .expect("signin response missing token")
            .to_string();
        (token, json)
    }

    /// Create a test user with a unique email and return `(user_id, session_token)`.
    pub async fn create_user_with_session(&self) -> (String, String) {
        let email = unique_email("harness");
        let (token, json) = self.signup(&email, "password123", "Test User").await;
        let user_id = json["user"]["id"]
            .as_str()
            .expect("missing user id")
            .to_string();
        (user_id, token)
    }

    pub async fn signup_user_fixture(&self, prefix: &str, name: &str) -> HarnessUser {
        let email = unique_email(prefix);
        let password = "password123".to_string();
        let (token, json) = self.signup(&email, &password, name).await;
        let user_id = json["user"]["id"]
            .as_str()
            .expect("signup response missing user.id")
            .to_string();
        HarnessUser {
            user_id,
            email,
            password,
            name: name.to_string(),
            token,
        }
    }

    pub async fn create_bootstrap_admin(&self, prefix: &str, name: &str) -> HarnessUser {
        let user = self.signup_user_fixture(prefix, name).await;
        self.set_global_role_direct(&user.user_id, "admin").await;
        user
    }

    pub async fn set_global_role_direct(&self, user_id: &str, role: &str) {
        self.auth
            .database()
            .update_user(
                user_id,
                athena_auth::types::UpdateUser {
                    role: Some(role.to_string()),
                    ..Default::default()
                },
            )
            .await
            .expect("direct role update should succeed");
    }

    pub async fn mark_user_email_verified(&self, email: &str) {
        let user = self
            .auth
            .database()
            .get_user_by_email(email)
            .await
            .expect("email lookup should succeed")
            .expect("user should exist");
        self.auth
            .database()
            .update_user(
                user.id(),
                athena_auth::types::UpdateUser {
                    email_verified: Some(true),
                    ..Default::default()
                },
            )
            .await
            .expect("email verification update should succeed");
    }

    pub async fn set_global_role_as_admin(
        &self,
        admin_token: &str,
        user_id: &str,
        role: &str,
    ) -> Value {
        let (status, body) = self
            .send(self.authed_post(
                "/admin/set-role",
                serde_json::json!({
                    "userId": user_id,
                    "role": role
                }),
                admin_token,
            ))
            .await;
        assert_eq!(status, 200, "admin set-role should succeed: {}", body);
        body
    }

    pub async fn admin_list_users(&self, token: &str) -> (u16, Value) {
        self.send(self.authed_get("/admin/list-users", token)).await
    }

    pub async fn admin_create_user(
        &self,
        token: &str,
        email: &str,
        password: &str,
        name: &str,
        role: &str,
    ) -> Value {
        let (status, body) = self
            .send(self.authed_post(
                "/admin/create-user",
                serde_json::json!({
                    "email": email,
                    "password": password,
                    "name": name,
                    "role": role
                }),
                token,
            ))
            .await;
        assert_eq!(status, 200, "admin create-user should succeed: {}", body);
        body
    }

    pub async fn admin_remove_user(&self, token: &str, user_id: &str) -> Value {
        let (status, body) = self
            .send(self.authed_post(
                "/admin/remove-user",
                serde_json::json!({ "userId": user_id }),
                token,
            ))
            .await;
        assert_eq!(status, 200, "admin remove-user should succeed: {}", body);
        body
    }

    pub async fn create_organization(
        &self,
        token: &str,
        name: &str,
        slug: &str,
    ) -> HarnessOrganization {
        let (status, body) = self
            .send(self.authed_post(
                "/organization/create",
                serde_json::json!({
                    "name": name,
                    "slug": slug
                }),
                token,
            ))
            .await;
        assert_eq!(status, 200, "organization create should succeed: {}", body);
        let id = body["id"]
            .as_str()
            .expect("organization create response missing id")
            .to_string();
        HarnessOrganization {
            id,
            name: name.to_string(),
            slug: slug.to_string(),
        }
    }

    pub async fn set_active_organization(&self, token: &str, slug: &str) {
        let (status, body) = self
            .send(self.authed_post(
                "/organization/set-active",
                serde_json::json!({ "organizationSlug": slug }),
                token,
            ))
            .await;
        assert_eq!(
            status, 200,
            "organization set-active should succeed: {}",
            body
        );
    }

    pub async fn invite_member(&self, token: &str, email: &str, role: &str) -> String {
        let (status, body) = self
            .send(self.authed_post(
                "/organization/invite-member",
                serde_json::json!({
                    "email": email,
                    "role": role
                }),
                token,
            ))
            .await;
        assert_eq!(
            status, 200,
            "organization invite-member should succeed: {}",
            body
        );
        body["id"]
            .as_str()
            .expect("invite-member response missing invitation id")
            .to_string()
    }

    pub async fn accept_invitation(&self, token: &str, invitation_id: &str) {
        let (status, body) = self
            .send(self.authed_post(
                "/organization/accept-invitation",
                serde_json::json!({ "invitationId": invitation_id }),
                token,
            ))
            .await;
        assert_eq!(
            status, 200,
            "organization accept-invitation should succeed: {}",
            body
        );
    }

    pub async fn organization_member_id_by_email(
        &self,
        token: &str,
        organization_slug: &str,
        email: &str,
    ) -> String {
        let (status, body) = self
            .send(get_with_auth_and_query(
                "/organization/get-full-organization",
                token,
                vec![("organizationSlug", organization_slug)],
            ))
            .await;
        assert_eq!(
            status, 200,
            "organization get-full-organization should succeed: {}",
            body
        );
        let members = body["members"]
            .as_array()
            .expect("get-full-organization response missing members array");
        members
            .iter()
            .find(|member| member["user"]["email"].as_str() == Some(email))
            .and_then(|member| member["id"].as_str())
            .expect("organization member lookup by email should succeed")
            .to_string()
    }

    pub async fn update_organization_member_role(
        &self,
        token: &str,
        member_id: &str,
        role: &str,
    ) -> Value {
        let (status, body) = self
            .send(self.authed_post(
                "/organization/update-member-role",
                serde_json::json!({
                    "memberId": member_id,
                    "role": role
                }),
                token,
            ))
            .await;
        assert_eq!(
            status, 200,
            "organization update-member-role should succeed: {}",
            body
        );
        body
    }

    pub async fn organization_has_permission(&self, token: &str, permissions: Value) -> Value {
        let (status, body) = self
            .send(self.authed_post(
                "/organization/has-permission",
                serde_json::json!({
                    "permissions": permissions
                }),
                token,
            ))
            .await;
        assert_eq!(
            status, 200,
            "organization has-permission should succeed: {}",
            body
        );
        body
    }

    pub async fn expect_user_missing(&self, user_id: &str) {
        let user = self
            .auth
            .database()
            .get_user_by_id(user_id)
            .await
            .expect("user lookup should succeed");
        assert!(user.is_none(), "expected user {user_id} to be deleted");
    }

    pub async fn expect_get_session_unauthorized(&self, token: &str) {
        let (status, body) = self.send(self.authed_get("/get-session", token)).await;
        assert_eq!(status, 401, "get-session should be unauthorized: {}", body);
    }
}
