#![allow(unused_imports)]
use super::*;
use crate::plugins::test_helpers;
use athena_auth_core::adapters::{GrantAssignmentOps, MemoryDatabaseAdapter, SessionOps, UserOps};
use athena_auth_core::{
    CreateSession, CreateUser, GrantDescriptor, ReplaceGrantAssignmentsForSource, Session,
};
use chrono::{Duration, Utc};

#[tokio::test]
async fn test_get_session_success() {
    let plugin = SessionManagementPlugin::new();
    let (ctx, _user, session) = test_helpers::create_test_context_with_user(
        CreateUser::new()
            .with_email("test@example.com")
            .with_name("Test User"),
        Duration::hours(24),
    )
    .await;

    let req = test_helpers::create_auth_request_no_query(
        HttpMethod::Get,
        "/get-session",
        Some(&session.token),
        None,
    );
    let response = plugin.handle_get_session(&req, &ctx).await.unwrap();

    assert_eq!(response.status, 200);

    let body_str = String::from_utf8(response.body).unwrap();
    let response_data: serde_json::Value = serde_json::from_str(&body_str).unwrap();
    assert_eq!(
        response_data["session"]["token"].as_str().unwrap(),
        session.token
    );
    assert_eq!(
        response_data["user"]["email"]
            .as_str()
            .map(|s| s.to_string()),
        Some("test@example.com".to_string())
    );
}

#[tokio::test]
async fn test_get_session_surfaces_native_grant_state() {
    let plugin = SessionManagementPlugin::new().with_grant_catalog(vec![GrantDescriptor {
        key: "payment-link.paid".to_string(),
        resource: "payment-link".to_string(),
        action: "paid".to_string(),
        description: "Grant access when a payment-link payment has succeeded.".to_string(),
    }]);
    let (ctx, user, session) = test_helpers::create_test_context_with_user(
        CreateUser::new()
            .with_email("grants@example.com")
            .with_name("Grant User"),
        Duration::hours(24),
    )
    .await;

    ctx.database
        .replace_grant_assignments_for_source(ReplaceGrantAssignmentsForSource {
            subject_kind: "user".to_string(),
            subject_id: user.id.clone(),
            source_kind: "payment".to_string(),
            source_ref: "pay_123".to_string(),
            grant_keys: vec!["payment-link.paid".to_string()],
            right_modules: vec![],
            metadata: serde_json::json!({ "origin": "billing.webhook" }),
        })
        .await
        .unwrap();

    let req = test_helpers::create_auth_request_no_query(
        HttpMethod::Get,
        "/get-session",
        Some(&session.token),
        None,
    );
    let response = plugin.handle_get_session(&req, &ctx).await.unwrap();

    assert_eq!(response.status, 200);

    let body_str = String::from_utf8(response.body).unwrap();
    let response_data: serde_json::Value = serde_json::from_str(&body_str).unwrap();
    assert_eq!(response_data["grants"]["subjectKind"], "user");
    assert_eq!(response_data["grants"]["subjectId"], user.id);
    assert_eq!(response_data["rights"]["subjectKind"], "user");
    assert_eq!(response_data["rights"]["subjectId"], user.id);
    assert_eq!(
        response_data["grants"]["grantKeys"],
        serde_json::json!(["payment-link.paid"])
    );
    assert_eq!(
        response_data["grants"]["rightKeys"],
        serde_json::json!(["payment-link.paid"])
    );
    assert_eq!(response_data["grants"]["rightModules"][0]["key"], "billing");
    assert_eq!(
        response_data["grants"]["rightModules"][0]["rightKeys"],
        serde_json::json!(["payment-link.paid"])
    );
    assert_eq!(
        response_data["grants"]["effectivePermissions"]["payment-link"],
        serde_json::json!(["paid"])
    );
    assert_eq!(
        response_data["grants"]["nativeRights"][0]["key"],
        "payment-link.paid"
    );
    assert_eq!(
        response_data["grants"]["nativeRightModules"][0]["key"],
        "billing"
    );
    assert_eq!(
        response_data["grants"]["nativeRightAssignments"][0]["nativeRight"]["key"],
        "payment-link.paid"
    );
    assert_eq!(
        response_data["grants"]["nativeRightAssignments"][0]["rightKey"],
        "payment-link.paid"
    );
    assert_eq!(
        response_data["grants"]["nativeRightAssignments"][0]["sourceKind"],
        "payment"
    );
    assert_eq!(
        response_data["grants"]["rightAssignments"][0]["grantKey"],
        "payment-link.paid"
    );
    assert_eq!(
        response_data["grants"]["resolvedRights"][0]["rightKey"],
        "payment-link.paid"
    );
    assert_eq!(
        response_data["rights"]["nativeRightAssignments"][0]["nativeRight"]["key"],
        "payment-link.paid"
    );
    assert_eq!(
        response_data["rights"]["rightAssignments"][0]["grantKey"],
        "payment-link.paid"
    );
    assert_eq!(
        response_data["rights"]["resolvedRights"][0]["rightKey"],
        "payment-link.paid"
    );
    assert_eq!(
        response_data["rights"]["resolvedNativeRights"][0]["rightKey"],
        "payment-link.paid"
    );
}

#[tokio::test]
async fn test_get_session_uses_native_right_catalog_without_manual_grant_descriptors() {
    let plugin = SessionManagementPlugin::new();
    let (ctx, user, session) = test_helpers::create_test_context_with_user(
        CreateUser::new()
            .with_email("native-rights@example.com")
            .with_name("Native Rights User"),
        Duration::hours(24),
    )
    .await;

    ctx.database
        .replace_grant_assignments_for_source(ReplaceGrantAssignmentsForSource {
            subject_kind: "user".to_string(),
            subject_id: user.id.clone(),
            source_kind: "payment".to_string(),
            source_ref: "pay_native".to_string(),
            grant_keys: vec!["payment-link.paid".to_string()],
            right_modules: vec![],
            metadata: serde_json::json!({ "origin": "billing.webhook" }),
        })
        .await
        .unwrap();

    let req = test_helpers::create_auth_request_no_query(
        HttpMethod::Get,
        "/get-session",
        Some(&session.token),
        None,
    );
    let response = plugin.handle_get_session(&req, &ctx).await.unwrap();

    assert_eq!(response.status, 200);

    let body_str = String::from_utf8(response.body).unwrap();
    let response_data: serde_json::Value = serde_json::from_str(&body_str).unwrap();
    assert_eq!(
        response_data["grants"]["effectivePermissions"]["payment-link"],
        serde_json::json!(["paid"])
    );
    assert_eq!(
        response_data["grants"]["resolvedGrants"][0]["grant"]["key"],
        "payment-link.paid"
    );
    assert_eq!(
        response_data["grants"]["resolvedNativeRights"][0]["nativeRight"]["source"],
        "billing"
    );
}

#[tokio::test]
async fn test_get_session_unauthorized() {
    let plugin = SessionManagementPlugin::new();
    let (ctx, _user, _session) = test_helpers::create_test_context_with_user(
        CreateUser::new()
            .with_email("test@example.com")
            .with_name("Test User"),
        Duration::hours(24),
    )
    .await;

    let req =
        test_helpers::create_auth_request_no_query(HttpMethod::Get, "/get-session", None, None);
    let response = plugin.handle_get_session(&req, &ctx).await.unwrap();
    assert_eq!(response.status, 401);

    let body_str = String::from_utf8(response.body).unwrap();
    let response_data: serde_json::Value = serde_json::from_str(&body_str).unwrap();
    assert_eq!(response_data["message"], "Authentication required");
    assert_eq!(response_data["code"], "SESSION_TOKEN_MISSING");
    assert_eq!(response_data["reason"], "missing_session_token");
    assert_eq!(response_data["session"], serde_json::Value::Null);
    assert_eq!(response_data["user"], serde_json::Value::Null);
    assert_eq!(
        response_data["diagnostics"]["sessionCookieName"],
        "athena-auth.session-token"
    );
    assert_eq!(
        response_data["diagnostics"]["authorizationHeaderPresent"],
        false
    );
    assert_eq!(response_data["diagnostics"]["sessionCookiePresent"], false);
}

#[tokio::test]
async fn test_get_session_invalid_cookie_returns_diagnostics_and_clear_cookie() {
    let plugin = SessionManagementPlugin::new();
    let (ctx, _user, _session) = test_helpers::create_test_context_with_user(
        CreateUser::new()
            .with_email("test@example.com")
            .with_name("Test User"),
        Duration::hours(24),
    )
    .await;

    let mut req =
        test_helpers::create_auth_request_no_query(HttpMethod::Get, "/get-session", None, None);
    req.headers.insert(
        "cookie".to_string(),
        "athena-auth.session-token=session_missing".to_string(),
    );
    req.query
        .insert("disableCookieCache".to_string(), "true".to_string());

    let response = plugin.handle_get_session(&req, &ctx).await.unwrap();
    assert_eq!(response.status, 401);
    assert!(
        response.headers.contains_key("Set-Cookie"),
        "invalid session cookies should be cleared"
    );

    let body_str = String::from_utf8(response.body).unwrap();
    let response_data: serde_json::Value = serde_json::from_str(&body_str).unwrap();
    assert_eq!(response_data["code"], "SESSION_NOT_FOUND");
    assert_eq!(response_data["reason"], "session_not_found_or_expired");
    assert_eq!(response_data["diagnostics"]["tokenSource"], "cookie");
    assert_eq!(response_data["diagnostics"]["cookieHeaderPresent"], true);
    assert_eq!(response_data["diagnostics"]["sessionCookiePresent"], true);
    assert_eq!(response_data["diagnostics"]["disableCookieCache"], "true");
}

#[tokio::test]
async fn test_sign_out_success() {
    let plugin = SessionManagementPlugin::new();
    let (ctx, _user, session) = test_helpers::create_test_context_with_user(
        CreateUser::new()
            .with_email("test@example.com")
            .with_name("Test User"),
        Duration::hours(24),
    )
    .await;

    let req = test_helpers::create_auth_request_no_query(
        HttpMethod::Post,
        "/sign-out",
        Some(&session.token),
        Some(b"{}".to_vec()),
    );
    let response = plugin.handle_sign_out(&req, &ctx).await.unwrap();

    assert_eq!(response.status, 200);

    let body_str = String::from_utf8(response.body).unwrap();
    let response_data: SuccessResponse = serde_json::from_str(&body_str).unwrap();
    assert!(response_data.success);

    let session_check = ctx.database.get_session(&session.token).await.unwrap();
    assert!(session_check.is_none());
}

#[tokio::test]
async fn test_list_sessions_success() {
    let plugin = SessionManagementPlugin::new();
    let (ctx, user, session) = test_helpers::create_test_context_with_user(
        CreateUser::new()
            .with_email("test@example.com")
            .with_name("Test User"),
        Duration::hours(24),
    )
    .await;

    let create_session2 = CreateSession {
        user_id: user.id.clone(),
        expires_at: Utc::now() + Duration::hours(24),
        ip_address: Some("192.168.1.1".to_string()),
        user_agent: Some("another-agent".to_string()),
        impersonated_by: None,
        active_organization_id: None,
    };
    ctx.database.create_session(create_session2).await.unwrap();

    let req = test_helpers::create_auth_request_no_query(
        HttpMethod::Get,
        "/list-sessions",
        Some(&session.token),
        None,
    );
    let response = plugin.handle_list_sessions(&req, &ctx).await.unwrap();

    assert_eq!(response.status, 200);

    let body_str = String::from_utf8(response.body).unwrap();
    let sessions: Vec<Session> = serde_json::from_str(&body_str).unwrap();
    assert_eq!(sessions.len(), 2);
}

#[tokio::test]
async fn test_list_sessions_excludes_expired_sessions_and_deactivates_them() {
    let plugin = SessionManagementPlugin::new();
    let (ctx, user, session) = test_helpers::create_test_context_with_user(
        CreateUser::new()
            .with_email("test@example.com")
            .with_name("Test User"),
        Duration::hours(24),
    )
    .await;

    let expired_session = ctx
        .database
        .create_session(CreateSession {
            user_id: user.id.clone(),
            expires_at: Utc::now() - Duration::minutes(5),
            ip_address: Some("192.168.1.2".to_string()),
            user_agent: Some("expired-agent".to_string()),
            impersonated_by: None,
            active_organization_id: None,
        })
        .await
        .unwrap();

    let req = test_helpers::create_auth_request_no_query(
        HttpMethod::Get,
        "/list-sessions",
        Some(&session.token),
        None,
    );
    let response = plugin.handle_list_sessions(&req, &ctx).await.unwrap();

    assert_eq!(response.status, 200);

    let body_str = String::from_utf8(response.body).unwrap();
    let sessions: Vec<Session> = serde_json::from_str(&body_str).unwrap();
    assert_eq!(sessions.len(), 1);
    assert_eq!(sessions[0].token, session.token);

    let expired_lookup = ctx
        .database
        .get_session(&expired_session.token)
        .await
        .unwrap();
    assert!(expired_lookup.is_none());
}

#[tokio::test]
async fn test_revoke_session_success() {
    let plugin = SessionManagementPlugin::new();
    let (ctx, user, session) = test_helpers::create_test_context_with_user(
        CreateUser::new()
            .with_email("test@example.com")
            .with_name("Test User"),
        Duration::hours(24),
    )
    .await;

    let create_session2 = CreateSession {
        user_id: user.id.clone(),
        expires_at: Utc::now() + Duration::hours(24),
        ip_address: Some("192.168.1.1".to_string()),
        user_agent: Some("another-agent".to_string()),
        impersonated_by: None,
        active_organization_id: None,
    };
    let session2 = ctx.database.create_session(create_session2).await.unwrap();

    let body = serde_json::json!({ "token": session2.token });
    let req = test_helpers::create_auth_request_no_query(
        HttpMethod::Post,
        "/revoke-session",
        Some(&session.token),
        Some(body.to_string().into_bytes()),
    );

    let response = plugin.handle_revoke_session(&req, &ctx).await.unwrap();
    assert_eq!(response.status, 200);
    assert!(
        response.headers.get("Set-Cookie").is_none(),
        "Revoking another session must not clear the current session cookie"
    );

    let session2_check = ctx.database.get_session(&session2.token).await.unwrap();
    assert!(session2_check.is_none());

    let session1_check = ctx.database.get_session(&session.token).await.unwrap();
    assert!(session1_check.is_some());
}

#[tokio::test]
async fn test_revoke_session_current_session_clears_cookie() {
    let plugin = SessionManagementPlugin::new();
    let (ctx, _user, session) = test_helpers::create_test_context_with_user(
        CreateUser::new()
            .with_email("test@example.com")
            .with_name("Test User"),
        Duration::hours(24),
    )
    .await;

    let body = serde_json::json!({ "token": session.token });
    let req = test_helpers::create_auth_request_no_query(
        HttpMethod::Post,
        "/revoke-session",
        Some(&session.token),
        Some(body.to_string().into_bytes()),
    );

    let response = plugin.handle_revoke_session(&req, &ctx).await.unwrap();
    assert_eq!(response.status, 200);

    let set_cookie = response
        .headers
        .get("Set-Cookie")
        .expect("revoke current session must set a clear-cookie header");
    assert!(
        set_cookie.contains(&ctx.config.session.cookie_name),
        "clear-cookie header must target configured session cookie"
    );
    assert!(
        set_cookie.contains("Expires="),
        "clear-cookie header must expire the cookie"
    );

    let session_check = ctx.database.get_session(&session.token).await.unwrap();
    assert!(session_check.is_none());
}

#[tokio::test]
async fn test_revoke_session_forbidden_different_user() {
    let plugin = SessionManagementPlugin::new();
    let (ctx, _user1, session1) = test_helpers::create_test_context_with_user(
        CreateUser::new()
            .with_email("test@example.com")
            .with_name("Test User"),
        Duration::hours(24),
    )
    .await;

    let create_user2 = CreateUser::new()
        .with_email("user2@example.com")
        .with_name("User Two");
    let user2 = ctx.database.create_user(create_user2).await.unwrap();

    let create_session2 = CreateSession {
        user_id: user2.id,
        expires_at: Utc::now() + Duration::hours(24),
        ip_address: Some("192.168.1.1".to_string()),
        user_agent: Some("another-agent".to_string()),
        impersonated_by: None,
        active_organization_id: None,
    };
    let session2 = ctx.database.create_session(create_session2).await.unwrap();

    let body = serde_json::json!({ "token": session2.token });
    let req = test_helpers::create_auth_request_no_query(
        HttpMethod::Post,
        "/revoke-session",
        Some(&session1.token),
        Some(body.to_string().into_bytes()),
    );

    let err = plugin.handle_revoke_session(&req, &ctx).await.unwrap_err();
    assert_eq!(err.status_code(), 403);
}

#[tokio::test]
async fn test_revoke_sessions_success() {
    let plugin = SessionManagementPlugin::new();
    let (ctx, user, session1) = test_helpers::create_test_context_with_user(
        CreateUser::new()
            .with_email("test@example.com")
            .with_name("Test User"),
        Duration::hours(24),
    )
    .await;

    let create_session2 = CreateSession {
        user_id: user.id.clone(),
        expires_at: Utc::now() + Duration::hours(24),
        ip_address: Some("192.168.1.1".to_string()),
        user_agent: Some("another-agent".to_string()),
        impersonated_by: None,
        active_organization_id: None,
    };
    ctx.database.create_session(create_session2).await.unwrap();

    let req = test_helpers::create_auth_request_no_query(
        HttpMethod::Post,
        "/revoke-sessions",
        Some(&session1.token),
        Some(b"{}".to_vec()),
    );
    let response = plugin.handle_revoke_sessions(&req, &ctx).await.unwrap();

    assert_eq!(response.status, 200);
    let set_cookie = response
        .headers
        .get("Set-Cookie")
        .expect("revoke-sessions must clear the current session cookie");
    assert!(
        set_cookie.contains(&ctx.config.session.cookie_name),
        "clear-cookie header must target configured session cookie"
    );
    assert!(
        set_cookie.contains("Expires="),
        "clear-cookie header must expire the cookie"
    );

    let user_sessions = ctx.database.get_user_sessions(&user.id).await.unwrap();
    assert_eq!(user_sessions.len(), 0);
}

#[tokio::test]
async fn test_revoke_other_sessions_does_not_clear_cookie() {
    let plugin = SessionManagementPlugin::new();
    let (ctx, user, session1) = test_helpers::create_test_context_with_user(
        CreateUser::new()
            .with_email("test@example.com")
            .with_name("Test User"),
        Duration::hours(24),
    )
    .await;

    let create_session2 = CreateSession {
        user_id: user.id.clone(),
        expires_at: Utc::now() + Duration::hours(24),
        ip_address: Some("192.168.1.1".to_string()),
        user_agent: Some("another-agent".to_string()),
        impersonated_by: None,
        active_organization_id: None,
    };
    let session2 = ctx.database.create_session(create_session2).await.unwrap();

    let req = test_helpers::create_auth_request_no_query(
        HttpMethod::Post,
        "/revoke-other-sessions",
        Some(&session1.token),
        Some(b"{}".to_vec()),
    );
    let response = plugin
        .handle_revoke_other_sessions(&req, &ctx)
        .await
        .unwrap();

    assert_eq!(response.status, 200);
    assert!(
        !response.headers.contains_key("Set-Cookie"),
        "revoke-other-sessions must keep the caller cookie"
    );

    let current_session = ctx.database.get_session(&session1.token).await.unwrap();
    assert!(current_session.is_some());
    let revoked_other = ctx.database.get_session(&session2.token).await.unwrap();
    assert!(revoked_other.is_none());
}

#[tokio::test]
async fn test_plugin_routes() {
    let plugin = SessionManagementPlugin::new();
    let routes = AuthPlugin::<MemoryDatabaseAdapter>::routes(&plugin);

    assert_eq!(routes.len(), 7);
    assert!(
        routes
            .iter()
            .any(|r| r.path == "/get-session" && r.method == HttpMethod::Get)
    );
    assert!(
        routes
            .iter()
            .any(|r| r.path == "/sign-out" && r.method == HttpMethod::Post)
    );
    assert!(
        routes
            .iter()
            .any(|r| r.path == "/list-sessions" && r.method == HttpMethod::Get)
    );
    assert!(
        routes
            .iter()
            .any(|r| r.path == "/revoke-session" && r.method == HttpMethod::Post)
    );
    assert!(
        routes
            .iter()
            .any(|r| r.path == "/revoke-sessions" && r.method == HttpMethod::Post)
    );
}

#[tokio::test]
async fn test_plugin_on_request_routing() {
    let plugin = SessionManagementPlugin::new();
    let (ctx, _user, session) = test_helpers::create_test_context_with_user(
        CreateUser::new()
            .with_email("test@example.com")
            .with_name("Test User"),
        Duration::hours(24),
    )
    .await;

    // Test valid route
    let req = test_helpers::create_auth_request_no_query(
        HttpMethod::Get,
        "/get-session",
        Some(&session.token),
        None,
    );
    let response = plugin.on_request(&req, &ctx).await.unwrap();
    assert!(response.is_some());
    assert_eq!(response.unwrap().status, 200);

    // Test invalid route
    let req = test_helpers::create_auth_request_no_query(
        HttpMethod::Get,
        "/invalid-route",
        Some(&session.token),
        None,
    );
    let response = plugin.on_request(&req, &ctx).await.unwrap();
    assert!(response.is_none());
}

#[tokio::test]
async fn test_configuration() {
    let plugin = SessionManagementPlugin::new()
        .enable_session_listing(false)
        .enable_session_revocation(false)
        .require_authentication(false);

    assert!(!plugin.config.enable_session_listing);
    assert!(!plugin.config.enable_session_revocation);
    assert!(!plugin.config.require_authentication);

    let (ctx, _user, session) = test_helpers::create_test_context_with_user(
        CreateUser::new()
            .with_email("test@example.com")
            .with_name("Test User"),
        Duration::hours(24),
    )
    .await;

    let req = test_helpers::create_auth_request_no_query(
        HttpMethod::Get,
        "/list-sessions",
        Some(&session.token),
        None,
    );
    let response = plugin.on_request(&req, &ctx).await.unwrap();
    assert!(response.is_none());

    let req = test_helpers::create_auth_request_no_query(
        HttpMethod::Post,
        "/revoke-session",
        Some(&session.token),
        Some(b"{}".to_vec()),
    );
    let response = plugin.on_request(&req, &ctx).await.unwrap();
    assert!(response.is_none());
}
