use reqwest::{Client, StatusCode};
use serde_json::json;

use crate::helpers::spawn_app_with_auth;

#[tokio::test]
async fn test_create_token_requires_authentication() {
    let app = spawn_app_with_auth().await;
    let client = Client::new();

    // Token creation now requires session or bearer token auth
    let response = client
        .post(&app.api_url("/tokens"))
        .json(&json!({ "name": "test-token" }))
        .send()
        .await
        .expect("Failed to send request");

    assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
}

#[tokio::test]
async fn test_create_token_with_bearer_auth() {
    let app = spawn_app_with_auth().await;
    let client = Client::new();
    let auth_token = app.auth_token.as_ref().unwrap();

    // Create a token using bearer auth
    let response = client
        .post(&app.api_url("/tokens"))
        .bearer_auth(auth_token)
        .json(&json!({ "name": "new-token" }))
        .send()
        .await
        .expect("Failed to send request");

    assert_eq!(response.status(), StatusCode::OK);

    let body: serde_json::Value = response.json().await.expect("Failed to parse response");
    assert!(body.get("id").is_some());
    assert_eq!(body.get("name").unwrap(), "new-token");
    assert!(body.get("token").is_some());

    // Token should be a non-empty string
    let token = body.get("token").unwrap().as_str().unwrap();
    assert!(!token.is_empty());
}

#[tokio::test]
async fn test_list_tokens_requires_authentication() {
    let app = spawn_app_with_auth().await;
    let client = Client::new();

    // Try to list tokens without authentication
    let response = client
        .get(&app.api_url("/tokens"))
        .send()
        .await
        .expect("Failed to send request");

    assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
}

#[tokio::test]
async fn test_list_tokens_with_authentication() {
    let app = spawn_app_with_auth().await;
    let client = Client::new();
    let auth_token = app.auth_token.as_ref().unwrap();

    // List tokens with authentication
    let response = client
        .get(&app.api_url("/tokens"))
        .bearer_auth(auth_token)
        .send()
        .await
        .expect("Failed to send request");

    assert_eq!(response.status(), StatusCode::OK);

    let tokens: Vec<serde_json::Value> = response.json().await.expect("Failed to parse response");
    // We expect at least 1 token (the one created by spawn_app_with_auth)
    assert!(
        !tokens.is_empty(),
        "Should have at least one token from test setup"
    );
    let test_token = tokens
        .iter()
        .find(|t| t.get("name").unwrap() == "test-token")
        .expect("Could not find test-token");
    assert_eq!(test_token.get("name").unwrap(), "test-token");
}

#[tokio::test]
async fn test_revoke_token() {
    let app = spawn_app_with_auth().await;
    let client = Client::new();
    let auth_token = app.auth_token.as_ref().unwrap();

    // Create a new token to revoke
    let create_response = client
        .post(&app.api_url("/tokens"))
        .bearer_auth(auth_token)
        .json(&json!({ "name": "token-to-revoke" }))
        .send()
        .await
        .expect("Failed to send request");

    let create_body: serde_json::Value = create_response
        .json()
        .await
        .expect("Failed to parse response");
    let token_id = create_body.get("id").unwrap().as_i64().unwrap();

    // Revoke the token
    let response = client
        .post(&app.api_url(&format!("/tokens/{}/revoke", token_id)))
        .bearer_auth(auth_token)
        .send()
        .await
        .expect("Failed to send request");

    assert_eq!(response.status(), StatusCode::OK);

    let body: serde_json::Value = response.json().await.expect("Failed to parse response");
    assert!(body.get("revoked_at").is_some());
}

#[tokio::test]
async fn test_revoked_token_cannot_be_used() {
    let app = spawn_app_with_auth().await;
    let client = Client::new();
    let auth_token = app.auth_token.as_ref().unwrap();

    // Create a new token
    let create_response = client
        .post(&app.api_url("/tokens"))
        .bearer_auth(auth_token)
        .json(&json!({ "name": "will-be-revoked" }))
        .send()
        .await
        .expect("Failed to send request");

    let create_body: serde_json::Value = create_response
        .json()
        .await
        .expect("Failed to parse response");
    let new_token = create_body.get("token").unwrap().as_str().unwrap();
    let token_id = create_body.get("id").unwrap().as_i64().unwrap();

    // Revoke it using the original auth token
    client
        .post(&app.api_url(&format!("/tokens/{}/revoke", token_id)))
        .bearer_auth(auth_token)
        .send()
        .await
        .expect("Failed to send request");

    // Try to use the revoked token
    let response = client
        .get(&app.api_url("/tokens"))
        .header("Authorization", format!("Bearer {}", new_token))
        .send()
        .await
        .expect("Failed to send request");

    assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
}

#[tokio::test]
async fn test_protected_endpoints_require_authentication() {
    let app = spawn_app_with_auth().await;
    let client = Client::new();

    // Try to create a roaster without authentication
    let response = client
        .post(&app.api_url("/roasters"))
        .json(&json!({
            "name": "Test Roaster",
            "country": "UK"
        }))
        .send()
        .await
        .expect("Failed to send request");

    assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
}

#[tokio::test]
async fn test_protected_endpoints_work_with_authentication() {
    let app = spawn_app_with_auth().await;
    let client = Client::new();
    let auth_token = app.auth_token.as_ref().unwrap();

    // Create a roaster with authentication
    let response = client
        .post(&app.api_url("/roasters"))
        .bearer_auth(auth_token)
        .json(&json!({
            "name": "Test Roaster",
            "country": "UK"
        }))
        .send()
        .await
        .expect("Failed to send request");

    assert_eq!(response.status(), StatusCode::CREATED);
}

#[tokio::test]
async fn test_invalid_session_cookie_fails() {
    let app = spawn_app_with_auth().await;
    let client = reqwest::Client::builder()
        .cookie_store(true)
        .build()
        .unwrap();

    // Try to create a roaster with no session (unauthenticated)
    let response = client
        .post(&app.api_url("/roasters"))
        .json(&json!({
            "name": "Invalid Session Roaster",
            "country": "UK"
        }))
        .send()
        .await
        .expect("Failed to send request");

    assert_eq!(
        response.status(),
        StatusCode::UNAUTHORIZED,
        "Request without valid session should fail"
    );
}

#[tokio::test]
async fn test_fake_session_cookie_fails() {
    let app = spawn_app_with_auth().await;
    let client = reqwest::Client::builder()
        .cookie_store(true)
        .build()
        .unwrap();

    // Try to use a fake/forged session cookie
    let response = client
        .post(&app.api_url("/roasters"))
        .header("Cookie", "brewlog_session=fake_session_token_12345")
        .json(&json!({
            "name": "Fake Session Roaster",
            "country": "IT"
        }))
        .send()
        .await
        .expect("Failed to send request");

    assert_eq!(
        response.status(),
        StatusCode::UNAUTHORIZED,
        "Fake session cookie should not authenticate"
    );
}

#[tokio::test]
async fn test_read_endpoints_dont_require_authentication() {
    let app = spawn_app_with_auth().await;
    let client = Client::new();

    // List roasters without authentication should work
    let response = client
        .get(&app.api_url("/roasters"))
        .send()
        .await
        .expect("Failed to send request");

    assert_eq!(response.status(), StatusCode::OK);
}
