// Copyright (c) 2019-2026 Provable Inc.
// This file is part of the snarkOS library.

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:

// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#[allow(dead_code)]
mod common;

use crate::common::primary::{TestNetwork, TestNetworkConfig};
use snarkos_node_bft::MAX_FETCH_TIMEOUT;

use std::time::Duration;

use deadline::deadline;
use tokio::time::sleep;

#[test_log::test(tokio::test(flavor = "multi_thread"))]
#[ignore = "long-running e2e test"]
async fn test_state_coherence() {
    const N: u16 = 4;
    const TRANSMISSION_INTERVAL_MS: u64 = 10;

    let mut network = tokio::task::spawn_blocking(|| {
        TestNetwork::new(TestNetworkConfig {
            num_nodes: N,
            bft: false,
            connect_all: true,
            fire_transmissions: Some(TRANSMISSION_INTERVAL_MS),
            log_connections: true,
        })
    })
    .await
    .unwrap();

    network.start().await;

    // TODO(nkls): the easiest would be to assert on the anchor or bullshark's output, once
    // implemented.

    std::future::pending::<()>().await;
}

#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn test_quorum_threshold() {
    // Start N nodes but don't connect them.
    const N: u16 = 4;
    const TRANSMISSION_INTERVAL_MS: u64 = 10;

    let mut network = tokio::task::spawn_blocking(|| {
        TestNetwork::new(TestNetworkConfig {
            num_nodes: N,
            bft: false,
            connect_all: false,
            fire_transmissions: None,
            log_connections: true,
        })
    })
    .await
    .unwrap();
    network.start().await;

    // Check each node is at round 1 (0 is genesis).
    for validators in network.validators.values() {
        assert_eq!(validators.primary.current_round(), 1);
    }

    // Start the cannons for node 0.
    network.fire_transmissions_at(0, TRANSMISSION_INTERVAL_MS);

    sleep(MAX_FETCH_TIMEOUT).await;

    // Check each node is still at round 1.
    for validator in network.validators.values() {
        assert_eq!(validator.primary.current_round(), 1);
    }

    // Connect the first two nodes and start the cannons for node 1.
    network.connect_validators(0, 1).await;
    network.fire_transmissions_at(1, TRANSMISSION_INTERVAL_MS);

    sleep(MAX_FETCH_TIMEOUT).await;

    // Check each node is still at round 1.
    for validator in network.validators.values() {
        assert_eq!(validator.primary.current_round(), 1);
    }

    // Connect the third node and start the cannons for it.
    network.connect_validators(0, 2).await;
    network.connect_validators(1, 2).await;
    network.fire_transmissions_at(2, TRANSMISSION_INTERVAL_MS);

    // Check the nodes reach quorum and advance through the rounds.
    const TARGET_ROUND: u64 = 4;
    let net = network.clone();
    deadline!(Duration::from_secs(20), move || { net.is_round_reached(TARGET_ROUND) });
}

#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn test_quorum_break() {
    // Start N nodes, connect them and start the cannons for each.
    const N: u16 = 4;
    const TRANSMISSION_INTERVAL_MS: u64 = 10;
    let mut network = tokio::task::spawn_blocking(|| {
        TestNetwork::new(TestNetworkConfig {
            num_nodes: N,
            bft: false,
            connect_all: true,
            fire_transmissions: Some(TRANSMISSION_INTERVAL_MS),
            log_connections: true,
        })
    })
    .await
    .unwrap();
    network.start().await;

    // Check the nodes have started advancing through the rounds.
    const TARGET_ROUND: u64 = 4;
    // Note: cloning the network is fine because the primaries it wraps are `Arc`ed.
    let network_clone = network.clone();
    deadline!(Duration::from_secs(20), move || { network_clone.is_round_reached(TARGET_ROUND) });

    // Break the quorum by disconnecting two nodes.
    const NUM_NODES: u16 = 2;
    network.disconnect(NUM_NODES).await;

    // Check the nodes have stopped advancing through the rounds.
    assert!(network.is_halted().await);
}

#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn test_storage_coherence() {
    // Start N nodes, connect them and start the cannons for each.
    const N: u16 = 4;
    const TRANSMISSION_INTERVAL_MS: u64 = 10;
    let mut network = tokio::task::spawn_blocking(|| {
        TestNetwork::new(TestNetworkConfig {
            num_nodes: N,
            bft: false,
            connect_all: true,
            fire_transmissions: Some(TRANSMISSION_INTERVAL_MS),
            log_connections: true,
        })
    })
    .await
    .unwrap();
    network.start().await;

    // Check the nodes have started advancing through the rounds.
    const TARGET_ROUND: u64 = 12;
    // Note: cloning the network is fine because the primaries it wraps are `Arc`ed.
    let network_clone = network.clone();
    deadline!(Duration::from_secs(40), move || { network_clone.is_round_reached(TARGET_ROUND) });

    // Check the committee is coherent across the network up to the target round. We skip the
    // genesis round.
    assert!(network.is_committee_coherent(1..TARGET_ROUND));

    // Check the round certificates are coherent across the network. We skip the genesis round and
    // check only up to 3 rounds before the the target round, because the network advances when
    // quorum is reached, not when all the nodes have completed the round and received certificates.
    assert!(network.is_certificate_round_coherent(1..TARGET_ROUND - 2));
}
