// file: kb_lib/src/launch_origin.rs

//! Launch surface attribution service.

#[derive(Debug, Copy, Clone)]
struct BuiltinLaunchSurfaceSpec {
    code: &'static str,
    name: &'static str,
    protocol_family: &'static str,
    enabled: bool,
}

const BUILTIN_LAUNCH_SURFACE_SPECS: &[BuiltinLaunchSurfaceSpec] = &[
    BuiltinLaunchSurfaceSpec {
        code: "raydium_launchpad",
        name: "Raydium LaunchLab / Launchpad",
        protocol_family: "raydium",
        enabled: true,
    },
    BuiltinLaunchSurfaceSpec {
        code: "letsbonk",
        name: "LetsBonk / Bonk.fun",
        protocol_family: "bonk",
        enabled: true,
    },
    BuiltinLaunchSurfaceSpec {
        code: "bonk_fun",
        name: "Bonk.fun",
        protocol_family: "bonk",
        enabled: true,
    },
    BuiltinLaunchSurfaceSpec {
        code: "bags",
        name: "Bags",
        protocol_family: "bags",
        enabled: true,
    },
    BuiltinLaunchSurfaceSpec {
        code: "moonshot",
        name: "Moonshot",
        protocol_family: "moonshot",
        enabled: true,
    },
    BuiltinLaunchSurfaceSpec {
        code: "moonit",
        name: "Moonit",
        protocol_family: "moonit",
        enabled: true,
    },
    BuiltinLaunchSurfaceSpec {
        code: "boop_fun",
        name: "Boop.fun",
        protocol_family: "boop",
        enabled: true,
    },
    BuiltinLaunchSurfaceSpec {
        code: "believe",
        name: "Believe",
        protocol_family: "believe",
        enabled: true,
    },
    BuiltinLaunchSurfaceSpec {
        code: "heaven",
        name: "Heaven",
        protocol_family: "heaven",
        enabled: false,
    },
];

/// Result of one launch surface attribution.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LaunchAttributionResult {
    /// Decoded event id.
    pub decoded_event_id: i64,
    /// Launch surface id.
    pub launch_surface_id: i64,
    /// Launch attribution id.
    pub launch_attribution_id: i64,
    /// Optional related pool id.
    pub pool_id: std::option::Option<i64>,
    /// Optional related pair id.
    pub pair_id: std::option::Option<i64>,
    /// Whether the attribution was newly created.
    pub created_attribution: bool,
}

/// Launch surface attribution service.
#[derive(Debug, Clone)]
pub struct LaunchOriginService {
    database: std::sync::Arc<crate::Database>,
    persistence: crate::DetectionPersistenceService,
}

impl LaunchOriginService {
    /// Creates a new launch-origin service.
    pub fn new(database: std::sync::Arc<crate::Database>) -> Self {
        let persistence = crate::DetectionPersistenceService::new(database.clone());
        return Self { database, persistence };
    }

    /// Ensures that every deferred built-in launch surface exists.
    pub async fn ensure_target_launch_surfaces(&self) -> Result<std::vec::Vec<i64>, crate::Error> {
        let mut surface_ids = std::vec::Vec::new();
        for spec in BUILTIN_LAUNCH_SURFACE_SPECS {
            let surface_id_result = self.ensure_builtin_launch_surface(spec.code).await;
            let surface_id = match surface_id_result {
                Ok(surface_id) => surface_id,
                Err(error) => return Err(error),
            };
            surface_ids.push(surface_id);
        }
        return Ok(surface_ids);
    }

    /// Ensures that one built-in launch surface exists and returns its id.
    pub async fn ensure_builtin_launch_surface(&self, code: &str) -> Result<i64, crate::Error> {
        let spec = match find_builtin_launch_surface_spec(code) {
            Some(spec) => spec,
            None => {
                return Err(crate::Error::InvalidState(format!(
                    "unknown built-in launch surface '{}'",
                    code
                )));
            },
        };
        let surface_id_result = self.ensure_launch_surface_from_spec(spec).await;
        let surface_id = match surface_id_result {
            Ok(surface_id) => surface_id,
            Err(error) => return Err(error),
        };
        if spec.code == "moonit" {
            let suffix_key_result = crate::query_launch_surface_keys_upsert(
                self.database.as_ref(),
                &crate::LaunchSurfaceKeyDto::new(
                    surface_id,
                    "token_mint_suffix".to_string(),
                    "moon".to_string(),
                ),
            )
            .await;
            if let Err(error) = suffix_key_result {
                return Err(error);
            }
        }
        return Ok(surface_id);
    }

    /// Ensures that the built-in `moonit` launch surface exists and returns its id.
    pub async fn ensure_moonit_surface(&self) -> Result<i64, crate::Error> {
        return self.ensure_builtin_launch_surface("moonit").await;
    }

    /// Ensures that the built-in `bags` launch surface exists and returns its id.
    pub async fn ensure_bags_surface(&self) -> Result<i64, crate::Error> {
        return self.ensure_builtin_launch_surface("bags").await;
    }

    /// Registers one generic launch-surface mapping from verified corpus/API evidence.
    pub async fn register_launch_surface_mapping(
        &self,
        surface_code: &str,
        token_mint: std::option::Option<&str>,
        config_account: std::option::Option<&str>,
        pool_account: std::option::Option<&str>,
        creator: std::option::Option<&str>,
    ) -> Result<i64, crate::Error> {
        let surface_id_result = self.ensure_builtin_launch_surface(surface_code).await;
        let surface_id = match surface_id_result {
            Ok(surface_id) => surface_id,
            Err(error) => return Err(error),
        };
        let key_specs = [
            ("token_mint", token_mint),
            ("config_account", config_account),
            ("pool_account", pool_account),
            ("creator", creator),
        ];
        let mut inserted_key_count = 0_u32;
        for (match_kind, match_value) in key_specs {
            let match_value = match match_value {
                Some(match_value) => match_value,
                None => continue,
            };
            if match_value.trim().is_empty() {
                continue;
            }
            let key_result = crate::query_launch_surface_keys_upsert(
                self.database.as_ref(),
                &crate::LaunchSurfaceKeyDto::new(
                    surface_id,
                    match_kind.to_string(),
                    match_value.to_string(),
                ),
            )
            .await;
            if let Err(error) = key_result {
                return Err(error);
            }
            inserted_key_count += 1;
        }
        if inserted_key_count == 0 {
            return Err(crate::Error::InvalidState(format!(
                "launch surface '{}' mapping must include at least one non-empty key",
                surface_code
            )));
        }
        return Ok(surface_id);
    }

    async fn ensure_launch_surface_from_spec(
        &self,
        spec: BuiltinLaunchSurfaceSpec,
    ) -> Result<i64, crate::Error> {
        let existing_result =
            crate::query_launch_surfaces_get_by_code(self.database.as_ref(), spec.code).await;
        let existing_option = match existing_result {
            Ok(existing_option) => existing_option,
            Err(error) => return Err(error),
        };
        match existing_option {
            Some(existing) => match existing.id {
                Some(surface_id) => return Ok(surface_id),
                None => {
                    return Err(crate::Error::InvalidState(format!(
                        "{} launch surface has no internal id",
                        spec.code
                    )));
                },
            },
            None => {
                let dto = crate::LaunchSurfaceDto::new(
                    spec.code.to_string(),
                    spec.name.to_string(),
                    Some(spec.protocol_family.to_string()),
                    spec.enabled,
                );
                return crate::query_launch_surfaces_upsert(self.database.as_ref(), &dto).await;
            },
        }
    }

    /// Registers one Bags pool mapping from the documented Bags API fields.
    pub async fn register_bags_pool_mapping(
        &self,
        token_mint: &str,
        dbc_config_key: std::option::Option<std::string::String>,
        dbc_pool_key: std::option::Option<std::string::String>,
        damm_v2_pool_key: std::option::Option<std::string::String>,
    ) -> Result<i64, crate::Error> {
        let surface_id_result = self
            .register_launch_surface_mapping(
                "bags",
                Some(token_mint),
                dbc_config_key.as_deref(),
                dbc_pool_key.as_deref(),
                None,
            )
            .await;
        let surface_id = match surface_id_result {
            Ok(surface_id) => surface_id,
            Err(error) => return Err(error),
        };
        if let Some(damm_v2_pool_key) = damm_v2_pool_key {
            let key_result = crate::query_launch_surface_keys_upsert(
                self.database.as_ref(),
                &crate::LaunchSurfaceKeyDto::new(
                    surface_id,
                    "pool_account".to_string(),
                    damm_v2_pool_key,
                ),
            )
            .await;
            if let Err(error) = key_result {
                return Err(error);
            }
        }
        return Ok(surface_id);
    }

    /// Attributes one transaction to known launch surfaces from decoded events.
    pub async fn attribute_transaction_by_signature(
        &self,
        signature: &str,
    ) -> Result<std::vec::Vec<crate::LaunchAttributionResult>, crate::Error> {
        let transaction_result =
            crate::query_chain_transactions_get_by_signature(self.database.as_ref(), signature)
                .await;
        let transaction_option = match transaction_result {
            Ok(transaction_option) => transaction_option,
            Err(error) => return Err(error),
        };
        let transaction = match transaction_option {
            Some(transaction) => transaction,
            None => {
                return Err(crate::Error::InvalidState(format!(
                    "cannot attribute unknown transaction '{}'",
                    signature
                )));
            },
        };
        let transaction_id = match transaction.id {
            Some(transaction_id) => transaction_id,
            None => {
                return Err(crate::Error::InvalidState(format!(
                    "transaction '{}' has no internal id",
                    signature
                )));
            },
        };
        let ensure_surfaces_result = self.ensure_target_launch_surfaces().await;
        if let Err(error) = ensure_surfaces_result {
            return Err(error);
        }
        let decoded_events_result = crate::query_dex_decoded_events_list_by_transaction_id(
            self.database.as_ref(),
            transaction_id,
        )
        .await;
        let decoded_events = match decoded_events_result {
            Ok(decoded_events) => decoded_events,
            Err(error) => return Err(error),
        };
        let mut results = std::vec::Vec::new();
        for decoded_event in &decoded_events {
            let candidate_result = self.match_surface_for_decoded_event(decoded_event).await;
            let candidate = match candidate_result {
                Ok(candidate) => candidate,
                Err(error) => return Err(error),
            };
            let candidate = match candidate {
                Some(candidate) => candidate,
                None => continue,
            };
            let decoded_event_id = match decoded_event.id {
                Some(decoded_event_id) => decoded_event_id,
                None => {
                    return Err(crate::Error::InvalidState(
                        "decoded event has no internal id".to_string(),
                    ));
                },
            };
            let pool_id = match decoded_event.pool_account.clone() {
                Some(pool_address) => {
                    let pool_result = crate::query_pools_get_by_address(
                        self.database.as_ref(),
                        pool_address.as_str(),
                    )
                    .await;
                    let pool_option = match pool_result {
                        Ok(pool_option) => pool_option,
                        Err(error) => return Err(error),
                    };
                    match pool_option {
                        Some(pool) => pool.id,
                        None => None,
                    }
                },
                None => None,
            };
            let pair_id = match pool_id {
                Some(pool_id) => {
                    let pair_result =
                        crate::query_pairs_get_by_pool_id(self.database.as_ref(), pool_id).await;
                    let pair_option = match pair_result {
                        Ok(pair_option) => pair_option,
                        Err(error) => return Err(error),
                    };
                    match pair_option {
                        Some(pair) => pair.id,
                        None => None,
                    }
                },
                None => None,
            };
            let existing_result = crate::query_launch_attributions_get_by_decoded_event_id(
                self.database.as_ref(),
                decoded_event_id,
            )
            .await;
            let existing_option = match existing_result {
                Ok(existing_option) => existing_option,
                Err(error) => return Err(error),
            };
            let created_attribution = existing_option.is_none();
            let attribution_id = if let Some(existing) = existing_option {
                match existing.id {
                    Some(existing_id) => existing_id,
                    None => {
                        return Err(crate::Error::InvalidState(
                            "launch attribution has no internal id".to_string(),
                        ));
                    },
                }
            } else {
                let dto = crate::LaunchAttributionDto::new(
                    candidate.launch_surface_id,
                    transaction_id,
                    decoded_event_id,
                    pool_id,
                    pair_id,
                    candidate.matched_key_id,
                    decoded_event.protocol_name.clone(),
                    candidate.match_kind.clone(),
                    candidate.matched_value.clone(),
                );
                let insert_result =
                    crate::query_launch_attributions_upsert(self.database.as_ref(), &dto).await;
                let attribution_id = match insert_result {
                    Ok(attribution_id) => attribution_id,
                    Err(error) => return Err(error),
                };
                let payload = serde_json::json!({
                    "surfaceCode": candidate.surface_code,
                    "surfaceName": candidate.surface_name,
                    "protocolName": decoded_event.protocol_name,
                    "eventKind": decoded_event.event_kind,
                    "matchKind": candidate.match_kind,
                    "matchedValue": candidate.matched_value,
                    "poolAccount": decoded_event.pool_account,
                    "transactionSignature": transaction.signature
                });
                let observation_result = self
                    .persistence
                    .record_observation(&crate::DetectionObservationInput::new(
                        "launch_surface.attribution".to_string(),
                        crate::ObservationSourceKind::Dex,
                        transaction.source_endpoint_name.clone(),
                        transaction.signature.clone(),
                        transaction.slot,
                        payload.clone(),
                    ))
                    .await;
                let observation_id = match observation_result {
                    Ok(observation_id) => observation_id,
                    Err(error) => return Err(error),
                };
                let signal_result = self
                    .persistence
                    .record_signal(&crate::DetectionSignalInput::new(
                        format!("signal.launch_surface.{}.detected", candidate.surface_code),
                        crate::AnalysisSignalSeverity::Low,
                        transaction.signature.clone(),
                        Some(observation_id),
                        None,
                        payload,
                    ))
                    .await;
                if let Err(error) = signal_result {
                    return Err(error);
                }
                attribution_id
            };
            results.push(crate::LaunchAttributionResult {
                decoded_event_id,
                launch_surface_id: candidate.launch_surface_id,
                launch_attribution_id: attribution_id,
                pool_id,
                pair_id,
                created_attribution,
            });
        }
        return Ok(results);
    }

    async fn match_surface_for_decoded_event(
        &self,
        decoded_event: &crate::DexDecodedEventDto,
    ) -> Result<std::option::Option<MatchedLaunchSurface>, crate::Error> {
        let mut candidates = std::vec::Vec::new();
        if let Some(config_account) = decoded_event.market_account.clone() {
            candidates.push(("config_account".to_string(), config_account));
        }
        if let Some(pool_account) = decoded_event.pool_account.clone() {
            candidates.push(("pool_account".to_string(), pool_account));
        }
        if let Some(token_a_mint) = decoded_event.token_a_mint.clone() {
            candidates.push(("token_mint".to_string(), token_a_mint));
        }
        if let Some(token_b_mint) = decoded_event.token_b_mint.clone() {
            candidates.push(("token_mint".to_string(), token_b_mint));
        }
        let payload_result =
            serde_json::from_str::<serde_json::Value>(decoded_event.payload_json.as_str());
        let payload = match payload_result {
            Ok(payload) => Some(payload),
            Err(_) => None,
        };
        if let Some(payload) = payload.as_ref() {
            let creator = extract_payload_string(
                payload,
                &["creator", "poolCreator", "user", "owner", "payer"],
            );
            if let Some(creator) = creator {
                candidates.push(("creator".to_string(), creator));
            }
        }
        for (match_kind, matched_value) in &candidates {
            let key_result = crate::query_launch_surface_keys_get_by_match(
                self.database.as_ref(),
                match_kind.as_str(),
                matched_value.as_str(),
            )
            .await;
            let key_option = match key_result {
                Ok(key_option) => key_option,
                Err(error) => return Err(error),
            };
            let key = match key_option {
                Some(key) => key,
                None => continue,
            };
            let matched_result = self
                .build_matched_surface_from_key(key, match_kind.clone(), matched_value.clone())
                .await;
            let matched_option = match matched_result {
                Ok(matched_option) => matched_option,
                Err(error) => return Err(error),
            };
            if matched_option.is_some() {
                return Ok(matched_option);
            }
        }
        let moonit_result = self.match_moonit_by_suffix(decoded_event).await;
        match moonit_result {
            Ok(moonit_result) => return Ok(moonit_result),
            Err(error) => return Err(error),
        }
    }

    async fn build_matched_surface_from_key(
        &self,
        key: crate::LaunchSurfaceKeyDto,
        match_kind: std::string::String,
        matched_value: std::string::String,
    ) -> Result<std::option::Option<MatchedLaunchSurface>, crate::Error> {
        let surface_result = crate::query_launch_surfaces_list(self.database.as_ref()).await;
        let surfaces = match surface_result {
            Ok(surfaces) => surfaces,
            Err(error) => return Err(error),
        };
        for surface in surfaces {
            let current_surface_id = match surface.id {
                Some(current_surface_id) => current_surface_id,
                None => continue,
            };
            if current_surface_id != key.launch_surface_id || !surface.is_enabled {
                continue;
            }
            return Ok(Some(MatchedLaunchSurface {
                launch_surface_id: current_surface_id,
                matched_key_id: key.id,
                surface_code: surface.code,
                surface_name: surface.name,
                match_kind,
                matched_value,
            }));
        }
        return Ok(None);
    }

    async fn match_moonit_by_suffix(
        &self,
        decoded_event: &crate::DexDecodedEventDto,
    ) -> Result<std::option::Option<MatchedLaunchSurface>, crate::Error> {
        let mut token_mints = std::vec::Vec::new();
        if let Some(token_a_mint) = decoded_event.token_a_mint.clone() {
            token_mints.push(token_a_mint);
        }
        if let Some(token_b_mint) = decoded_event.token_b_mint.clone() {
            token_mints.push(token_b_mint);
        }
        let mut matched_token_option = None;
        for token_mint in token_mints {
            if token_mint.ends_with("moon") {
                matched_token_option = Some(token_mint);
                break;
            }
        }
        let matched_token = match matched_token_option {
            Some(matched_token) => matched_token,
            None => return Ok(None),
        };
        let surface_id_result = self.ensure_moonit_surface().await;
        let surface_id = match surface_id_result {
            Ok(surface_id) => surface_id,
            Err(error) => return Err(error),
        };
        let key_result = crate::query_launch_surface_keys_get_by_match(
            self.database.as_ref(),
            "token_mint_suffix",
            "moon",
        )
        .await;
        let key_option = match key_result {
            Ok(key_option) => key_option,
            Err(error) => return Err(error),
        };
        let matched_key_id = match key_option {
            Some(key) => key.id,
            None => None,
        };
        return Ok(Some(MatchedLaunchSurface {
            launch_surface_id: surface_id,
            matched_key_id,
            surface_code: "moonit".to_string(),
            surface_name: "Moonit".to_string(),
            match_kind: "token_mint_suffix".to_string(),
            matched_value: matched_token,
        }));
    }
}

fn find_builtin_launch_surface_spec(code: &str) -> std::option::Option<BuiltinLaunchSurfaceSpec> {
    for spec in BUILTIN_LAUNCH_SURFACE_SPECS {
        if spec.code == code {
            return Some(*spec);
        }
    }
    return None;
}

#[derive(Debug, Clone)]
struct MatchedLaunchSurface {
    launch_surface_id: i64,
    matched_key_id: std::option::Option<i64>,
    surface_code: std::string::String,
    surface_name: std::string::String,
    match_kind: std::string::String,
    matched_value: std::string::String,
}

fn extract_payload_string(
    value: &serde_json::Value,
    candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
    if let Some(object) = value.as_object() {
        for candidate_key in candidate_keys {
            let direct_option = object.get(*candidate_key);
            if let Some(direct) = direct_option {
                let text_option = direct.as_str();
                if let Some(text) = text_option {
                    return Some(text.to_string());
                }
            }
        }
        for nested_value in object.values() {
            let nested_result = extract_payload_string(nested_value, candidate_keys);
            if nested_result.is_some() {
                return nested_result;
            }
        }
        return None;
    }
    if let Some(array) = value.as_array() {
        for nested_value in array {
            let nested_result = extract_payload_string(nested_value, candidate_keys);
            if nested_result.is_some() {
                return nested_result;
            }
        }
    }
    return None;
}

#[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("launch_origin.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_fun_launch_registry(database: std::sync::Arc<crate::Database>) {
        let surface_id_result = crate::query_launch_surfaces_upsert(
            database.as_ref(),
            &crate::LaunchSurfaceDto::new(
                "meteora_fun_launch".to_string(),
                "Meteora Fun Launch".to_string(),
                Some("meteora".to_string()),
                true,
            ),
        )
        .await;
        let surface_id = match surface_id_result {
            Ok(surface_id) => surface_id,
            Err(error) => panic!("launch surface upsert must succeed: {}", error),
        };
        let key_result = crate::query_launch_surface_keys_upsert(
            database.as_ref(),
            &crate::LaunchSurfaceKeyDto::new(
                surface_id,
                "config_account".to_string(),
                "DbcDetectConfig111".to_string(),
            ),
        )
        .await;
        if let Err(error) = key_result {
            panic!("launch surface key upsert must succeed: {}", error);
        }
    }

    async fn seed_decoded_meteora_dbc_event(
        database: std::sync::Arc<crate::Database>,
        signature: &str,
    ) {
        let transaction_model = crate::TransactionModelService::new(database.clone());
        let dex_decode = crate::DexDecodeService::new(database.clone());
        let dex_detect = crate::DexDetectService::new(database);
        let resolved_transaction = serde_json::json!({
            "slot": 910004,
            "blockTime": 1779100004,
            "version": 0,
            "transaction": {
                "message": {
                    "instructions": [
                        {
                            "programId": crate::METEORA_DBC_PROGRAM_ID,
                            "program": "meteora_dbc",
                            "stackHeight": 1,
                            "accounts": [
                                "DbcDetectPool111",
                                "DbcDetectTokenA111",
                                crate::WSOL_MINT_ID,
                                "DbcDetectConfig111",
                                "DbcDetectCreator111"
                            ],
                            "parsed": {
                                "info": {
                                    "pool": "DbcDetectPool111",
                                    "baseMint": "DbcDetectTokenA111",
                                    "quoteMint": crate::WSOL_MINT_ID,
                                    "poolConfig": "DbcDetectConfig111",
                                    "creator": "DbcDetectCreator111"
                                }
                            },
                            "data": "opaque"
                        }
                    ]
                }
            },
            "meta": {
                "err": null,
                "logMessages": [
                    "Program log: Instruction: CreatePool"
                ]
            }
        });
        let project_result = transaction_model
            .persist_resolved_transaction(
                signature,
                Some("helius_primary_http".to_string()),
                &resolved_transaction,
            )
            .await;
        if let Err(error) = project_result {
            panic!("projection must succeed: {}", error);
        }
        let decode_result = dex_decode.decode_transaction_by_signature(signature).await;
        if let Err(error) = decode_result {
            panic!("dex decode must succeed: {}", error);
        }
        let detect_result = dex_detect.detect_transaction_by_signature(signature).await;
        if let Err(error) = detect_result {
            panic!("dex detect must succeed: {}", error);
        }
    }

    #[tokio::test]
    async fn attribute_transaction_by_signature_detects_meteora_fun_launch() {
        let database = make_database().await;
        seed_fun_launch_registry(database.clone()).await;
        seed_decoded_meteora_dbc_event(database.clone(), "sig-launch-origin-1").await;
        let service = crate::LaunchOriginService::new(database.clone());
        let result = service.attribute_transaction_by_signature("sig-launch-origin-1").await;
        let results = match result {
            Ok(results) => results,
            Err(error) => panic!("launch attribution must succeed: {}", error),
        };
        assert_eq!(results.len(), 1);
        assert!(results[0].created_attribution);
        let list_result = crate::query_launch_attributions_list_by_pool_id(
            database.as_ref(),
            results[0].pool_id.unwrap(),
        )
        .await;
        let listed = match list_result {
            Ok(listed) => listed,
            Err(error) => panic!("launch attribution list must succeed: {}", error),
        };
        assert_eq!(listed.len(), 1);
        assert_eq!(listed[0].match_kind, "config_account".to_string());
        assert_eq!(listed[0].matched_value, "DbcDetectConfig111".to_string());
    }

    async fn seed_decoded_meteora_damm_v2_event_with_moon_token(
        database: std::sync::Arc<crate::Database>,
        signature: &str,
    ) {
        let transaction_model = crate::TransactionModelService::new(database.clone());
        let dex_decode = crate::DexDecodeService::new(database.clone());
        let dex_detect = crate::DexDetectService::new(database);
        let resolved_transaction = serde_json::json!({
            "slot": 910105,
            "blockTime": 1779100105,
            "version": 0,
            "transaction": {
                "message": {
                    "instructions": [
                        {
                            "programId": crate::METEORA_DAMM_V2_PROGRAM_ID,
                            "program": "meteora_damm_v2",
                            "stackHeight": 1,
                            "accounts": [
                                "MoonitDammV2Pool111",
                                "ExampleTokenmoon",
                                crate::WSOL_MINT_ID,
                                "MoonitDammV2Config111",
                                "MoonitCreator111"
                            ],
                            "parsed": {
                                "info": {
                                    "instruction": "initialize_customizable_pool",
                                    "pool": "MoonitDammV2Pool111",
                                    "tokenAMint": "ExampleTokenmoon",
                                    "tokenBMint": crate::WSOL_MINT_ID,
                                    "creator": "MoonitCreator111",
                                    "isCustomizablePool": true
                                }
                            },
                            "data": "opaque"
                        }
                    ]
                }
            },
            "meta": {
                "err": null,
                "logMessages": [
                    "Program log: Instruction: InitializeCustomizablePool"
                ]
            }
        });
        let project_result = transaction_model
            .persist_resolved_transaction(
                signature,
                Some("helius_primary_http".to_string()),
                &resolved_transaction,
            )
            .await;
        if let Err(error) = project_result {
            panic!("projection must succeed: {}", error);
        }
        let decode_result = dex_decode.decode_transaction_by_signature(signature).await;
        if let Err(error) = decode_result {
            panic!("dex decode must succeed: {}", error);
        }
        let detect_result = dex_detect.detect_transaction_by_signature(signature).await;
        if let Err(error) = detect_result {
            panic!("dex detect must succeed: {}", error);
        }
    }

    async fn seed_bags_registry(database: std::sync::Arc<crate::Database>) {
        let service = crate::LaunchOriginService::new(database);
        let register_result = service
            .register_bags_pool_mapping(
                "DbcDetectTokenA111",
                Some("DbcDetectConfig111".to_string()),
                Some("DbcDetectPool111".to_string()),
                Some("BagsMigratedPool111".to_string()),
            )
            .await;
        if let Err(error) = register_result {
            panic!("bags registry must succeed: {}", error);
        }
    }

    #[tokio::test]
    async fn attribute_transaction_by_signature_detects_bags_from_registered_mapping() {
        let database = make_database().await;
        seed_bags_registry(database.clone()).await;
        seed_decoded_meteora_dbc_event(database.clone(), "sig-launch-origin-bags-1").await;
        let service = crate::LaunchOriginService::new(database.clone());
        let result = service.attribute_transaction_by_signature("sig-launch-origin-bags-1").await;
        let results = match result {
            Ok(results) => results,
            Err(error) => panic!("bags attribution must succeed: {}", error),
        };
        assert_eq!(results.len(), 1);
        assert!(results[0].created_attribution);
        let surface_result =
            crate::query_launch_surfaces_get_by_code(database.as_ref(), "bags").await;
        let surface_option = match surface_result {
            Ok(surface_option) => surface_option,
            Err(error) => panic!("bags surface fetch must succeed: {}", error),
        };
        let surface = match surface_option {
            Some(surface) => surface,
            None => panic!("bags surface must exist"),
        };
        assert_eq!(surface.id, Some(results[0].launch_surface_id));
        let listed_result = crate::query_launch_attributions_list_by_pool_id(
            database.as_ref(),
            results[0].pool_id.unwrap(),
        )
        .await;
        let listed = match listed_result {
            Ok(listed) => listed,
            Err(error) => panic!("bags attribution list must succeed: {}", error),
        };
        assert_eq!(listed.len(), 1);
        assert_eq!(listed[0].match_kind, "config_account".to_string());
        assert_eq!(listed[0].matched_value, "DbcDetectConfig111".to_string());
    }

    #[tokio::test]
    async fn attribute_transaction_by_signature_detects_moonit_from_token_suffix() {
        let database = make_database().await;
        seed_decoded_meteora_damm_v2_event_with_moon_token(
            database.clone(),
            "sig-launch-origin-moonit-1",
        )
        .await;
        let service = crate::LaunchOriginService::new(database.clone());
        let result = service.attribute_transaction_by_signature("sig-launch-origin-moonit-1").await;
        let results = match result {
            Ok(results) => results,
            Err(error) => panic!("moonit attribution must succeed: {}", error),
        };
        assert_eq!(results.len(), 1);
        assert!(results[0].created_attribution);
        let surface_result =
            crate::query_launch_surfaces_get_by_code(database.as_ref(), "moonit").await;
        let surface_option = match surface_result {
            Ok(surface_option) => surface_option,
            Err(error) => panic!("moonit surface fetch must succeed: {}", error),
        };
        let surface = match surface_option {
            Some(surface) => surface,
            None => panic!("moonit surface must exist"),
        };
        assert_eq!(surface.id, Some(results[0].launch_surface_id));
        let listed_result = crate::query_launch_attributions_list_by_pool_id(
            database.as_ref(),
            results[0].pool_id.unwrap(),
        )
        .await;
        let listed = match listed_result {
            Ok(listed) => listed,
            Err(error) => panic!("moonit attribution list must succeed: {}", error),
        };
        assert_eq!(listed.len(), 1);
        assert_eq!(listed[0].match_kind, "token_mint_suffix".to_string());
        assert_eq!(listed[0].matched_value, "ExampleTokenmoon".to_string());
    }
    #[tokio::test]
    async fn ensure_target_launch_surfaces_seeds_0_7_39_surfaces_without_program_ids() {
        let database = make_database().await;
        let service = crate::LaunchOriginService::new(database.clone());
        let result = service.ensure_target_launch_surfaces().await;
        let surface_ids = match result {
            Ok(surface_ids) => surface_ids,
            Err(error) => panic!("target launch surfaces must seed: {}", error),
        };
        assert_eq!(surface_ids.len(), 9);
        let required_codes = [
            "raydium_launchpad",
            "letsbonk",
            "bonk_fun",
            "bags",
            "moonshot",
            "moonit",
            "boop_fun",
            "believe",
            "heaven",
        ];
        for code in required_codes {
            let surface_result =
                crate::query_launch_surfaces_get_by_code(database.as_ref(), code).await;
            let surface_option = match surface_result {
                Ok(surface_option) => surface_option,
                Err(error) => panic!("surface fetch must succeed: {}", error),
            };
            assert!(surface_option.is_some(), "missing launch surface {}", code);
        }
        let heaven_surface =
            match crate::query_launch_surfaces_get_by_code(database.as_ref(), "heaven").await {
                Ok(Some(surface)) => surface,
                Ok(None) => panic!("heaven surface must exist"),
                Err(error) => panic!("heaven surface fetch must succeed: {}", error),
            };
        assert!(!heaven_surface.is_enabled);
    }

    #[tokio::test]
    async fn attribute_transaction_by_signature_detects_generic_launch_surface_mapping() {
        let database = make_database().await;
        let service = crate::LaunchOriginService::new(database.clone());
        let register_result = service
            .register_launch_surface_mapping(
                "raydium_launchpad",
                Some("DbcDetectTokenA111"),
                Some("DbcDetectConfig111"),
                None,
                None,
            )
            .await;
        if let Err(error) = register_result {
            panic!("launchpad mapping registration must succeed: {}", error);
        }
        seed_decoded_meteora_dbc_event(database.clone(), "sig-launch-origin-launchpad-1").await;
        let result = service
            .attribute_transaction_by_signature("sig-launch-origin-launchpad-1")
            .await;
        let results = match result {
            Ok(results) => results,
            Err(error) => panic!("launchpad attribution must succeed: {}", error),
        };
        assert_eq!(results.len(), 1);
        assert!(results[0].created_attribution);
        let surface_result =
            crate::query_launch_surfaces_get_by_code(database.as_ref(), "raydium_launchpad").await;
        let surface = match surface_result {
            Ok(Some(surface)) => surface,
            Ok(None) => panic!("raydium_launchpad surface must exist"),
            Err(error) => panic!("surface fetch must succeed: {}", error),
        };
        assert_eq!(surface.id, Some(results[0].launch_surface_id));
    }
}
