use axum::body::Body;
use axum::http::{Request, StatusCode};
use axum::routing::post;
use axum::Router;
use ed25519_dalek::SigningKey;
use sqlx::sqlite::SqlitePoolOptions;
use tower::ServiceExt;
use wiremock::matchers::{any, method, path_regex};
use wiremock::{Mock, MockServer, ResponseTemplate};

use clean_copy_server::crypto;
use clean_copy_server::handlers::activate;
use clean_copy_server::AppState;

fn payment_intent_json(id: &str, status: &str) -> serde_json::Value {
  serde_json::json!({
      "id": id,
      "object": "payment_intent",
      "status": status,
      "amount": 2900,
      "amount_capturable": 0,
      "amount_received": 0,
      "currency": "usd",
      "capture_method": "automatic",
      "confirmation_method": "automatic",
      "created": 1700000000,
      "livemode": false,
      "metadata": {},
      "payment_method_types": ["card"],
  })
}

async fn setup_app(mock_url: &str) -> (Router, SigningKey) {
  let pool = SqlitePoolOptions::new()
    .connect("sqlite::memory:")
    .await
    .unwrap();

  sqlx::migrate!().run(&pool).await.unwrap();

  let key = SigningKey::from_bytes(&[42u8; 32]);
  let stripe_client = stripe::Client::from_url(mock_url, "sk_test_fake");

  let state = AppState {
    config: clean_copy_server::config::Config {
      stripe_secret_key: "sk_test_fake".to_string(),
      stripe_price_id: String::new(),
      signing_key_hex: hex::encode(key.to_bytes()),
      database_url: "sqlite::memory:".to_string(),
      release_dir: "/tmp/releases".to_string(),
      static_dir: "./static".to_string(),
      port: 3000,
      app_url: "http://localhost:1420".to_string(),
    },
    db: pool,
    signing_key: key.clone(),
    stripe_client,
  };

  let app = Router::new()
    .route("/api/activate", post(activate::activate))
    .with_state(state);

  (app, key)
}

#[tokio::test]
async fn test_activate_new_license_with_mock_stripe() {
  let mock_server = MockServer::start().await;

  // Match ANY request to see what the Stripe client actually sends
  Mock::given(any())
    .respond_with(
      ResponseTemplate::new(200).set_body_json(payment_intent_json("pi_test_123", "succeeded")),
    )
    .expect(1)
    .mount(&mock_server)
    .await;

  let (app, key) = setup_app(&mock_server.uri()).await;

  let req_body = serde_json::json!({
      "license_key": "pi_test_123",
      "hwid": "hwid_device_1"
  });

  let response = app
    .oneshot(
      Request::builder()
        .method("POST")
        .uri("/api/activate")
        .header("content-type", "application/json")
        .body(Body::from(serde_json::to_string(&req_body).unwrap()))
        .unwrap(),
    )
    .await
    .unwrap();

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

  let body: serde_json::Value = axum::body::to_bytes(response.into_body(), usize::MAX)
    .await
    .map(|b| serde_json::from_slice(&b).unwrap())
    .unwrap();

  let token = body["license_token"].as_str().unwrap();
  assert!(!token.is_empty());

  let pub_key = key.verifying_key().to_bytes();
  let payload = crypto::verify_license(token, &pub_key, "hwid_device_1").unwrap();
  assert_eq!(payload.license_key, "pi_test_123");
  assert_eq!(payload.device_count, Some(1));
}

#[tokio::test]
async fn test_activate_payment_not_succeeded() {
  let mock_server = MockServer::start().await;

  Mock::given(any())
    .respond_with(
      ResponseTemplate::new(200)
        .set_body_json(payment_intent_json("pi_pending", "requires_payment_method")),
    )
    .expect(1)
    .mount(&mock_server)
    .await;

  let (app, _key) = setup_app(&mock_server.uri()).await;

  let req_body = serde_json::json!({
      "license_key": "pi_pending",
      "hwid": "hwid_device_1"
  });

  let response = app
    .oneshot(
      Request::builder()
        .method("POST")
        .uri("/api/activate")
        .header("content-type", "application/json")
        .body(Body::from(serde_json::to_string(&req_body).unwrap()))
        .unwrap(),
    )
    .await
    .unwrap();

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

  let body: serde_json::Value = axum::body::to_bytes(response.into_body(), usize::MAX)
    .await
    .map(|b| serde_json::from_slice(&b).unwrap())
    .unwrap();

  assert!(body["error"]
    .as_str()
    .unwrap()
    .contains("Payment has not been completed"));
}

#[tokio::test]
async fn test_activate_payment_not_found() {
  let mock_server = MockServer::start().await;

  Mock::given(method("GET"))
    .and(path_regex(r"/v1/payment_intents/pi_nonexistent"))
    .respond_with(ResponseTemplate::new(404).set_body_json(serde_json::json!({
        "error": {
            "type": "invalid_request_error",
            "message": "No such PaymentIntent: 'pi_nonexistent'",
        }
    })))
    .expect(1)
    .mount(&mock_server)
    .await;

  let (app, _key) = setup_app(&mock_server.uri()).await;

  let req_body = serde_json::json!({
      "license_key": "pi_nonexistent",
      "hwid": "hwid_device_1"
  });

  let response = app
    .oneshot(
      Request::builder()
        .method("POST")
        .uri("/api/activate")
        .header("content-type", "application/json")
        .body(Body::from(serde_json::to_string(&req_body).unwrap()))
        .unwrap(),
    )
    .await
    .unwrap();

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

  let body: serde_json::Value = axum::body::to_bytes(response.into_body(), usize::MAX)
    .await
    .map(|b| serde_json::from_slice(&b).unwrap())
    .unwrap();

  assert!(body["error"]
    .as_str()
    .unwrap()
    .contains("Payment not found"));
}

#[tokio::test]
async fn test_activate_missing_fields() {
  let mock_server = MockServer::start().await;
  let (app, _key) = setup_app(&mock_server.uri()).await;

  let req_body = serde_json::json!({
      "license_key": "",
      "hwid": "hwid_device_1"
  });

  let response = app
    .oneshot(
      Request::builder()
        .method("POST")
        .uri("/api/activate")
        .header("content-type", "application/json")
        .body(Body::from(serde_json::to_string(&req_body).unwrap()))
        .unwrap(),
    )
    .await
    .unwrap();

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

#[tokio::test]
async fn test_activate_idempotent_same_device() {
  let mock_server = MockServer::start().await;

  Mock::given(any())
    .respond_with(
      ResponseTemplate::new(200).set_body_json(payment_intent_json("pi_test_123", "succeeded")),
    )
    .expect(2)
    .mount(&mock_server)
    .await;

  let (app, key) = setup_app(&mock_server.uri()).await;

  let req_body = serde_json::json!({
      "license_key": "pi_test_123",
      "hwid": "hwid_device_1"
  });

  let response1 = app
    .clone()
    .oneshot(
      Request::builder()
        .method("POST")
        .uri("/api/activate")
        .header("content-type", "application/json")
        .body(Body::from(serde_json::to_string(&req_body).unwrap()))
        .unwrap(),
    )
    .await
    .unwrap();
  assert_eq!(response1.status(), StatusCode::OK);

  let response2 = app
    .oneshot(
      Request::builder()
        .method("POST")
        .uri("/api/activate")
        .header("content-type", "application/json")
        .body(Body::from(serde_json::to_string(&req_body).unwrap()))
        .unwrap(),
    )
    .await
    .unwrap();
  assert_eq!(response2.status(), StatusCode::OK);

  let body2: serde_json::Value = axum::body::to_bytes(response2.into_body(), usize::MAX)
    .await
    .map(|b| serde_json::from_slice(&b).unwrap())
    .unwrap();

  let pub_key = key.verifying_key().to_bytes();
  let payload = crypto::verify_license(
    body2["license_token"].as_str().unwrap(),
    &pub_key,
    "hwid_device_1",
  )
  .unwrap();
  assert_eq!(payload.device_count, Some(1));
}
