#![allow(missing_docs)]
//! End-to-end smart filter test: TaskFilter + GatewayClient
//!
//! Simulates the chain watcher's main loop logic:
//! 1. Gateway marks task in Redis (SADD) → watcher skips it (already seen)
//! 2. Direct on-chain task (not in Redis) → watcher relays to gateway via HTTP POST
//!
//! Requires: Redis at localhost:6379
//! Run with: cargo test -p newton-chain-watcher --test smart_filter_e2e -- --ignored

use alloy::primitives::{Address, Bytes, FixedBytes, B256, U256};
use newton_chain_watcher::{
    event::{ChainEvent, ChainEventType},
    filter::TaskFilter,
    gateway_client::GatewayClient,
};
use newton_core::newton_prover_task_manager::{INewtonProverTaskManager::Task, NewtonMessage};
use redis::AsyncCommands;
use wiremock::{
    matchers::{method, path},
    Mock, MockServer, ResponseTemplate,
};

const TEST_CHAIN_ID: u64 = 55555;
const REDIS_URL: &str = "redis://:redis@localhost:6379";

fn make_task(task_id_byte: u8) -> Task {
    Task {
        taskId: B256::from([task_id_byte; 32]),
        policyClient: Address::ZERO,
        taskCreatedBlock: 10,
        quorumThresholdPercentage: 67,
        intent: NewtonMessage::Intent {
            from: Address::ZERO,
            to: Address::ZERO,
            value: U256::ZERO,
            data: Bytes::default(),
            chainId: U256::from(TEST_CHAIN_ID),
            functionSignature: Bytes::default(),
        },
        intentSignature: Bytes::default(),
        wasmArgs: Bytes::default(),
        quorumNumbers: Bytes::from(vec![0]),
        initializationTimestamp: U256::ZERO,
    }
}

fn make_chain_event(task_id_byte: u8) -> ChainEvent {
    let task = make_task(task_id_byte);
    ChainEvent {
        chain_id: TEST_CHAIN_ID,
        event_type: ChainEventType::DirectOnchainTask {
            task_id: task.taskId,
            task: Box::new(task),
        },
        block_number: 100,
        tx_hash: B256::ZERO,
    }
}

async fn cleanup_redis(chain_id: u64) {
    let key = TaskFilter::redis_key(chain_id);
    let client = redis::Client::open(REDIS_URL).expect("redis client");
    let mut conn = redis::aio::ConnectionManager::new(client).await.expect("redis conn");
    let _: Result<(), _> = conn.del(&key).await;
}

/// Full smart filter flow: gateway-seen tasks are skipped, direct tasks are relayed.
///
/// Simulates the chain watcher main loop without needing WebSocket or anvil.
#[tokio::test]
#[ignore]
async fn smart_filter_skips_gateway_tasks_relays_direct_tasks() {
    // Setup
    cleanup_redis(TEST_CHAIN_ID).await;

    let filter = TaskFilter::new(REDIS_URL, TEST_CHAIN_ID)
        .await
        .expect("redis connection");

    // Start a mock gateway that records incoming events
    let mock_gateway = MockServer::start().await;
    Mock::given(method("POST"))
        .and(path("/watcher"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "status": "accepted"
        })))
        .expect(1) // Only the direct task should arrive
        .mount(&mock_gateway)
        .await;

    let gateway_client = GatewayClient::new(format!("{}/watcher", mock_gateway.uri()), Some(1));

    // --- Simulate gateway marking task 0xAA as seen (offchain path) ---
    let seen_task_id = FixedBytes::from([0xAAu8; 32]);
    filter.mark_seen(&seen_task_id).await.expect("mark_seen");

    // --- Simulate two on-chain events arriving at the watcher ---
    let events = vec![
        make_chain_event(0xAA), // Gateway-originated (already in Redis)
        make_chain_event(0xBB), // Direct on-chain (NOT in Redis)
    ];

    // Replicate the main loop filter logic from lib.rs
    for event in &events {
        match &event.event_type {
            ChainEventType::DirectOnchainTask { task_id, .. } => {
                if filter.is_seen_by_gateway(task_id).await {
                    // Skip — gateway already handled this task
                    continue;
                }
                // Relay direct on-chain task to gateway
                gateway_client
                    .submit_event(event)
                    .await
                    .expect("submit_event should succeed");
            }
            _ => {
                // Always relay operator events
                gateway_client
                    .submit_event(event)
                    .await
                    .expect("submit_event should succeed");
            }
        }
    }

    // Verification: wiremock asserts exactly 1 request was received (the 0xBB task)
    // The 0xAA task was filtered out by the Redis SET check
    // (wiremock's .expect(1) will panic on drop if count doesn't match)

    cleanup_redis(TEST_CHAIN_ID).await;
}

/// Verifies that operator events bypass the filter and are always relayed.
#[tokio::test]
#[ignore]
async fn operator_events_always_relayed() {
    cleanup_redis(TEST_CHAIN_ID).await;

    let mock_gateway = MockServer::start().await;
    Mock::given(method("POST"))
        .and(path("/watcher"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "status": "accepted"
        })))
        .expect(2) // Both operator events should arrive
        .mount(&mock_gateway)
        .await;

    let gateway_client = GatewayClient::new(format!("{}/watcher", mock_gateway.uri()), Some(1));

    let events = vec![
        ChainEvent {
            chain_id: TEST_CHAIN_ID,
            event_type: ChainEventType::OperatorAdded {
                operator: Address::ZERO,
                operator_set_avs: Address::ZERO,
                operator_set_id: 0,
            },
            block_number: 200,
            tx_hash: B256::ZERO,
        },
        ChainEvent {
            chain_id: TEST_CHAIN_ID,
            event_type: ChainEventType::OperatorRemoved {
                operator: Address::ZERO,
                operator_set_avs: Address::ZERO,
                operator_set_id: 0,
            },
            block_number: 201,
            tx_hash: B256::ZERO,
        },
    ];

    // Operator events do not go through the filter
    for event in &events {
        gateway_client
            .submit_event(event)
            .await
            .expect("submit_event should succeed");
    }

    // wiremock's .expect(2) verifies both were relayed

    cleanup_redis(TEST_CHAIN_ID).await;
}
