//! Test hrana related functionalities
#![allow(deprecated)]

use futures::SinkExt as _;
use libsql::Database;
use libsql_server::{
    auth::{
        user_auth_strategies::{self, jwt::Token},
        Auth,
    },
    config::UserApiConfig,
};
use tempfile::tempdir;
use tokio_stream::StreamExt;
use tokio_tungstenite::{
    client_async,
    tungstenite::{self, client::IntoClientRequest},
};
use turmoil::net::TcpStream;

use crate::common::{
    auth::{encode, key_pair},
    net::{init_tracing, SimServer, TestServer, TurmoilConnector},
};

async fn make_standalone_server(auth_strategy: Auth) -> Result<(), Box<dyn std::error::Error>> {
    init_tracing();
    let tmp = tempdir()?;
    let server = TestServer {
        path: tmp.path().to_owned().into(),
        user_api_config: UserApiConfig {
            hrana_ws_acceptor: None,
            auth_strategy,
            ..Default::default()
        },
        ..Default::default()
    };

    server.start_sim(8080).await?;

    Ok(())
}

fn gen_test_jwt_auth() -> (Auth, String) {
    let (encoding_key, decoding_key) = key_pair();
    let jwt_keys = vec![jsonwebtoken::DecodingKey::from_ed_components(&decoding_key).unwrap()];

    let auth = Auth::new(user_auth_strategies::Jwt::new(jwt_keys));

    let claims = Token::default();

    let token = encode(&claims, &encoding_key);

    (auth, token)
}

#[test]
fn http_hrana() {
    let (auth, token) = gen_test_jwt_auth();

    let mut sim = turmoil::Builder::new().build();
    sim.host("primary", move || make_standalone_server(auth.clone()));
    sim.client("client", async {
        let db =
            Database::open_remote_with_connector("http://primary:8080", token, TurmoilConnector)?;
        let conn = db.connect()?;

        conn.execute("create table t(x text)", ()).await?;

        Ok(())
    });

    sim.run().unwrap();
}

#[test]
fn embedded_replica() {
    let tmp_embedded = tempdir().unwrap();
    let tmp_embedded_path = tmp_embedded.path().to_owned();

    let (auth, token) = gen_test_jwt_auth();

    let mut sim = turmoil::Builder::new().build();
    sim.host("primary", move || make_standalone_server(auth.clone()));

    sim.client("client", async move {
        let path = tmp_embedded_path.join("embedded");

        let db = Database::open_with_remote_sync_connector(
            path.to_str().unwrap(),
            "http://primary:8080",
            token,
            TurmoilConnector,
            false,
            None,
        )
        .await?;

        let conn = db.connect()?;

        conn.execute("create table t(x text)", ()).await?;

        Ok(())
    });

    sim.run().unwrap();
}

#[test]
fn ws_hrana() {
    let (auth, token) = gen_test_jwt_auth();

    let mut sim = turmoil::Builder::new().build();
    sim.host("primary", move || make_standalone_server(auth.clone()));

    sim.client("client", async move {
        let url = "ws://primary:8080";

        let req = url.into_client_request().unwrap();

        let conn = TcpStream::connect("primary:8080").await.unwrap();

        let (mut ws, _) = client_async(req, conn).await.unwrap();

        #[derive(serde::Serialize, Debug)]
        #[serde(tag = "type", rename_all = "snake_case")]
        pub enum ClientMsg {
            Hello { jwt: Option<String> },
        }

        #[derive(serde::Deserialize, Debug)]
        #[serde(tag = "type", rename_all = "snake_case")]
        pub enum ServerMsg {
            HelloOk {},
        }

        let msg = ClientMsg::Hello {
            jwt: Some(token.to_string()),
        };

        let msg_data = serde_json::to_string(&msg).unwrap();

        ws.send(tungstenite::Message::Text(msg_data)).await.unwrap();

        let Some(tungstenite::Message::Text(msg)) = ws.try_next().await.unwrap() else {
            panic!("wrong message type");
        };

        serde_json::from_str::<ServerMsg>(&msg).unwrap();

        Ok(())
    });

    sim.run().unwrap();
}