use bft_crdt_derive::add_crdt_fields;
use bft_json_crdt::keypair::KeyPair;
use bft_json_crdt::{
    json_crdt::{BaseCrdt, CrdtNode, IntoCrdtNode},
    keypair::{make_keypair, ED25519_PUBLIC_KEY_LENGTH},
    list_crdt::ListCrdt,
    op::ROOT_ID,
};
use serde::{Deserialize, Serialize};
use tokio::time::{self};
use websockets::WebSocket;

/// Starts a websocket and periodically sends a BFT-CRDT message to the websocket server
pub(crate) async fn start() -> Result<(), websockets::WebSocketError> {
    println!("connecting to websocket at ws://localhost:8080/");
    let mut ws = WebSocket::connect("ws://localhost:8080/").await?;

    // generate a placeholder transaction
    let json = generate_transaction().unwrap();

    // set up a new BFT-CRDT
    let keys = make_keypair();
    let mut bft_crdt = BaseCrdt::<ListExample>::new(&keys);
    println!("Generated a new CRDT with public key: {}", keys.public());

    // next job is to keep adding to this guy
    let _a = bft_crdt.doc.list.insert(ROOT_ID, 'a').sign(&keys);

    let mut interval = every_two_seconds();
    loop {
        interval.tick().await;
        println!("Sending: {}", json);
        ws.send_text(json.clone()).await?;

        let msg = ws.receive().await?;
        println!("Received: {:?}", msg);
        // TODO:  bft_crdt.apply() changes in here when we receive socket input from other nodes
        bft_crdt.apply(msg);
        println!("New crdt state is: {}", bft_crdt.doc.view())
    }
}

fn every_two_seconds() -> time::Interval {
    time::interval(time::Duration::from_secs(2))
}

#[add_crdt_fields]
#[derive(Clone, CrdtNode, Serialize, Deserialize)]
struct ListExample {
    list: ListCrdt<Transaction>, // switch to Transaction as soon as char is working
}

/// A fake Transaction struct we can use as a simulated payload
#[add_crdt_fields]
#[derive(Clone, CrdtNode, Serialize, Deserialize)]
struct Transaction {
    from: String,
    to: String,
    amount: f64,
}

fn generate_transaction() -> serde_json::Result<String> {
    let transaction = Transaction {
        from: "Alice".to_string(),
        to: "Bob".to_string(),
        amount: 100.0,
        path: vec![],
        id: [0; ED25519_PUBLIC_KEY_LENGTH],
    };

    let json = serde_json::to_string(&transaction).unwrap();
    Ok(json)
}
