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

use clean_copy_server::handlers::{activate, checkout};
use clean_copy_server::AppState;

async fn setup_app(mock_url: &str) -> Router {
  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: "price_test_123".to_string(),
      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:3000".to_string(),
    },
    db: pool,
    signing_key: key,
    stripe_client,
  };

  Router::new()
    .route("/api/checkout", get(checkout::checkout))
    .route("/api/activate", axum::routing::post(activate::activate))
    .with_state(state)
}

fn checkout_session_json(id: &str, url: Option<&str>) -> serde_json::Value {
  let mut json = serde_json::json!({
      "id": id,
      "object": "checkout.session",
      "mode": "payment",
      "automatic_tax": { "enabled": false },
      "created": 1700000000,
      "expires_at": 1700003600,
      "custom_fields": [],
      "custom_text": {
          "after_submit": null,
          "shipping_address": null,
          "submit": null,
          "terms_of_service_acceptance": null
      },
      "livemode": false,
      "payment_method_types": ["card"],
      "payment_status": "unpaid",
      "shipping_options": [],
  });
  if let Some(u) = url {
    json["url"] = serde_json::json!(u);
  }
  json
}

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

  Mock::given(method("POST"))
    .and(path_regex(r"/v1/checkout/sessions"))
    .respond_with(
      ResponseTemplate::new(200).set_body_json(checkout_session_json(
        "cs_test_123",
        Some("https://checkout.stripe.com/pay/cs_test_123"),
      )),
    )
    .expect(1)
    .mount(&mock_server)
    .await;

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

  let response = app
    .oneshot(
      Request::builder()
        .method("GET")
        .uri("/api/checkout")
        .body(Body::empty())
        .unwrap(),
    )
    .await
    .unwrap();

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

  let location = response
    .headers()
    .get("location")
    .unwrap()
    .to_str()
    .unwrap();
  assert_eq!(location, "https://checkout.stripe.com/pay/cs_test_123");
}

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

  Mock::given(method("POST"))
    .and(path_regex(r"/v1/checkout/sessions"))
    .respond_with(ResponseTemplate::new(400).set_body_json(serde_json::json!({
        "error": {
            "type": "invalid_request_error",
            "message": "No such price: 'price_bad'",
        }
    })))
    .expect(1)
    .mount(&mock_server)
    .await;

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

  let response = app
    .oneshot(
      Request::builder()
        .method("GET")
        .uri("/api/checkout")
        .body(Body::empty())
        .unwrap(),
    )
    .await
    .unwrap();

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

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

  Mock::given(method("POST"))
    .and(path_regex(r"/v1/checkout/sessions"))
    .respond_with(
      ResponseTemplate::new(200).set_body_json(checkout_session_json("cs_test_no_url", None)),
    )
    .expect(1)
    .mount(&mock_server)
    .await;

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

  let response = app
    .oneshot(
      Request::builder()
        .method("GET")
        .uri("/api/checkout")
        .body(Body::empty())
        .unwrap(),
    )
    .await
    .unwrap();

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