// file: kb_lib/src/ws_hybrid_runtime.rs

//! Hybrid WebSocket runtime facade.

/// Facade service combining hybrid watch-target collection and
/// hybrid technical observation recording.
#[derive(Debug, Clone)]
pub struct WsHybridRuntimeService {
    watch_service: crate::WsHybridWatchService,
    observation_service: crate::WsHybridObservationService,
}

impl WsHybridRuntimeService {
    /// Creates a new hybrid WebSocket runtime facade.
    pub fn new(database: std::sync::Arc<crate::Database>) -> Self {
        let watch_service = crate::WsHybridWatchService::new(database.clone());
        let observation_service = crate::WsHybridObservationService::new(database);
        return Self { watch_service, observation_service };
    }

    /// Collects the current hybrid watch snapshot.
    pub async fn collect_watch_snapshot(
        &self,
    ) -> Result<crate::WsHybridWatchSnapshot, crate::Error> {
        return self.watch_service.collect_snapshot().await;
    }

    /// Records one `logsNotification` payload.
    pub async fn record_logs_notification(
        &self,
        endpoint_name: std::option::Option<std::string::String>,
        payload: &serde_json::Value,
    ) -> Result<crate::WsHybridObservationResult, crate::Error> {
        return self.observation_service.record_logs_notification(endpoint_name, payload).await;
    }

    /// Records one `programNotification` payload.
    pub async fn record_program_notification(
        &self,
        endpoint_name: std::option::Option<std::string::String>,
        watched_program_id: std::string::String,
        payload: &serde_json::Value,
    ) -> Result<crate::WsHybridObservationResult, crate::Error> {
        return self
            .observation_service
            .record_program_notification(endpoint_name, watched_program_id, payload)
            .await;
    }

    /// Records one `accountNotification` payload.
    pub async fn record_account_notification(
        &self,
        endpoint_name: std::option::Option<std::string::String>,
        watched_account: std::string::String,
        payload: &serde_json::Value,
    ) -> Result<crate::WsHybridObservationResult, crate::Error> {
        return self
            .observation_service
            .record_account_notification(endpoint_name, watched_account, payload)
            .await;
    }
}

#[cfg(test)]
mod tests {
    async fn make_database() -> std::sync::Arc<crate::Database> {
        let tempdir_result = tempfile::tempdir();
        let tempdir = match tempdir_result {
            Ok(tempdir) => tempdir,
            Err(error) => panic!("tempdir must succeed: {}", error),
        };
        let database_path = tempdir.path().join("ws_hybrid_runtime.sqlite3");
        let config = crate::DatabaseConfig {
            enabled: true,
            backend: crate::DatabaseBackend::Sqlite,
            sqlite: crate::SqliteDatabaseConfig {
                path: database_path.to_string_lossy().to_string(),
                create_if_missing: true,
                busy_timeout_ms: 5000,
                max_connections: 1,
                auto_initialize_schema: true,
                use_wal: true,
            },
        };
        let database_result = crate::Database::connect_and_initialize(&config).await;
        let database = match database_result {
            Ok(database) => database,
            Err(error) => panic!("database init must succeed: {}", error),
        };
        return std::sync::Arc::new(database);
    }

    async fn seed_dexes_and_pools(database: std::sync::Arc<crate::Database>) {
        let dex_a = crate::DexDto::new(
            "raydium_amm_v4".to_string(),
            "Raydium AmmV4".to_string(),
            Some("RaydiumProgram111".to_string()),
            None,
            true,
        );
        let dex_a_id_result = crate::query_dexs_upsert(database.as_ref(), &dex_a).await;
        let dex_a_id = match dex_a_id_result {
            Ok(dex_a_id) => dex_a_id,
            Err(error) => panic!("dex A upsert must succeed: {}", error),
        };
        let dex_b = crate::DexDto::new(
            "meteora_dbc".to_string(),
            "Meteora DBC".to_string(),
            Some("MeteoraProgram111".to_string()),
            Some("SharedRouter111".to_string()),
            true,
        );
        let dex_b_id_result = crate::query_dexs_upsert(database.as_ref(), &dex_b).await;
        let dex_b_id = match dex_b_id_result {
            Ok(dex_b_id) => dex_b_id,
            Err(error) => panic!("dex B upsert must succeed: {}", error),
        };
        let dex_c = crate::DexDto::new(
            "pump_swap".to_string(),
            "PumpSwap".to_string(),
            Some("PumpSwapProgram111".to_string()),
            Some("SharedRouter111".to_string()),
            true,
        );
        let dex_c_id_result = crate::query_dexs_upsert(database.as_ref(), &dex_c).await;
        let dex_c_id = match dex_c_id_result {
            Ok(dex_c_id) => dex_c_id,
            Err(error) => panic!("dex C upsert must succeed: {}", error),
        };
        let pool_a = crate::PoolDto::new(
            dex_a_id,
            "PoolAddress111".to_string(),
            crate::PoolKind::Amm,
            crate::PoolStatus::Active,
        );
        let pool_a_result = crate::query_pools_upsert(database.as_ref(), &pool_a).await;
        if let Err(error) = pool_a_result {
            panic!("pool A upsert must succeed: {}", error);
        }
        let pool_b = crate::PoolDto::new(
            dex_b_id,
            "PoolAddress222".to_string(),
            crate::PoolKind::Amm,
            crate::PoolStatus::Active,
        );
        let pool_b_result = crate::query_pools_upsert(database.as_ref(), &pool_b).await;
        if let Err(error) = pool_b_result {
            panic!("pool B upsert must succeed: {}", error);
        }
        let pool_c = crate::PoolDto::new(
            dex_c_id,
            "PoolAddress333".to_string(),
            crate::PoolKind::Amm,
            crate::PoolStatus::Inactive,
        );
        let pool_c_result = crate::query_pools_upsert(database.as_ref(), &pool_c).await;
        if let Err(error) = pool_c_result {
            panic!("pool C upsert must succeed: {}", error);
        }
    }

    #[tokio::test]
    async fn collect_watch_snapshot_combines_program_and_account_targets() {
        let database = make_database().await;
        seed_dexes_and_pools(database.clone()).await;
        let service = crate::WsHybridRuntimeService::new(database);
        let snapshot_result = service.collect_watch_snapshot().await;
        let snapshot = match snapshot_result {
            Ok(snapshot) => snapshot,
            Err(error) => panic!("collect watch snapshot must succeed: {}", error),
        };
        assert_eq!(snapshot.program_targets.len(), 4);
        assert_eq!(snapshot.account_targets.len(), 2);
    }

    #[tokio::test]
    async fn record_notifications_are_exposed_through_runtime_facade() {
        let database = make_database().await;
        let service = crate::WsHybridRuntimeService::new(database);
        let logs_payload = serde_json::json!({
            "method": "logsNotification",
            "params": {
                "result": {
                    "context": {
                        "slot": 123
                    },
                    "value": {
                        "signature": "RuntimeLogsSig111",
                        "err": null,
                        "logs": [
                            "Program log: Instruction: InitializePool"
                        ]
                    }
                }
            }
        });
        let logs_result = service
            .record_logs_notification(Some("mainnet_public_ws_logs".to_string()), &logs_payload)
            .await;
        let logs_observation = match logs_result {
            Ok(logs_observation) => logs_observation,
            Err(error) => panic!("logs record must succeed: {}", error),
        };
        assert!(logs_observation.created_observation);
        let program_payload = serde_json::json!({
            "method": "programNotification",
            "params": {
                "result": {
                    "context": {
                        "slot": 124
                    },
                    "value": {
                        "pubkey": "RuntimeProgramOwned111",
                        "account": {
                            "lamports": 1,
                            "owner": "Program111"
                        }
                    }
                }
            }
        });
        let program_result = service
            .record_program_notification(
                Some("helius_primary_ws_programs".to_string()),
                "Program111".to_string(),
                &program_payload,
            )
            .await;
        let program_observation = match program_result {
            Ok(program_observation) => program_observation,
            Err(error) => panic!("program record must succeed: {}", error),
        };
        assert!(program_observation.created_observation);
        let account_payload = serde_json::json!({
            "method": "accountNotification",
            "params": {
                "result": {
                    "context": {
                        "slot": 125
                    },
                    "value": {
                        "lamports": 10,
                        "owner": "Program111"
                    }
                }
            }
        });
        let account_result = service
            .record_account_notification(
                Some("mainnet_public_ws_accounts".to_string()),
                "RuntimePool111".to_string(),
                &account_payload,
            )
            .await;
        let account_observation = match account_result {
            Ok(account_observation) => account_observation,
            Err(error) => panic!("account record must succeed: {}", error),
        };
        assert!(account_observation.created_observation);
    }
}
