// file: kb_lib/src/db/dtos/dex_event_coverage_entry.rs

//! Application-facing DTOs for DEX event coverage.

/// Application-facing coverage row for one upstream/local event entry.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct DexEventCoverageEntryDto {
    /// Optional numeric primary key.
    pub id: std::option::Option<i64>,
    /// Stable unique key used for idempotent upserts.
    pub coverage_key: std::string::String,
    /// Internal decoder code.
    pub decoder_code: std::string::String,
    /// Optional Solana program id associated with the source entry.
    pub program_id: std::option::Option<std::string::String>,
    /// Program family.
    pub program_family: std::string::String,
    /// Surface kind such as AMM, CLMM, launch, orderbook or aggregator.
    pub surface_kind: std::string::String,
    /// Source repository or registry source.
    pub source_repo: std::option::Option<std::string::String>,
    /// Source path inside the repository or registry source.
    pub source_path: std::option::Option<std::string::String>,
    /// Entry kind such as instruction, event, account, log or program_data.
    pub entry_kind: std::string::String,
    /// Source entry name.
    pub entry_name: std::string::String,
    /// Optional discriminator encoded as lowercase hexadecimal.
    pub discriminator_hex: std::option::Option<std::string::String>,
    /// Optional discriminator byte length.
    pub discriminator_len: std::option::Option<i64>,
    /// Normalized event family.
    pub event_family: std::option::Option<std::string::String>,
    /// Expected DB target table or audit-only target.
    pub expected_db_target: std::option::Option<std::string::String>,
    /// Proof status for this entry.
    pub proof_status: std::string::String,
    /// Local event kind produced by the decoder, when any.
    pub local_event_kind: std::option::Option<std::string::String>,
    /// Number of observed decoded/audit rows in the local corpus.
    pub observed_count: i64,
    /// Number of rows materialized into specialized business tables.
    pub materialized_count: i64,
    /// Number of trade rows produced from this entry.
    pub trade_count: i64,
    /// First local signature observed for this entry.
    pub first_signature: std::option::Option<std::string::String>,
    /// Last local signature observed for this entry.
    pub last_signature: std::option::Option<std::string::String>,
    /// Notes preserving uncertainty, mapping decisions or DB gaps.
    pub notes: std::option::Option<std::string::String>,
    /// Creation timestamp.
    pub created_at: chrono::DateTime<chrono::Utc>,
    /// Update timestamp.
    pub updated_at: chrono::DateTime<chrono::Utc>,
}

impl DexEventCoverageEntryDto {
    /// Audit-only target for decoded events that should not leave `k_sol_dex_decoded_events`.
    pub const DB_TARGET_DECODED_EVENTS_ONLY: &'static str = "k_sol_dex_decoded_events_only";
    /// Target table for validated AMM/CLOB trade rows.
    pub const DB_TARGET_TRADE_EVENTS: &'static str = "k_sol_trade_events";
    /// Target table for validated liquidity add/remove rows.
    pub const DB_TARGET_LIQUIDITY_EVENTS: &'static str = "k_sol_liquidity_events";
    /// Target table for validated pool lifecycle and position lifecycle rows.
    pub const DB_TARGET_POOL_LIFECYCLE_EVENTS: &'static str = "k_sol_pool_lifecycle_events";
    /// Target table for validated fee rows.
    pub const DB_TARGET_FEE_EVENTS: &'static str = "k_sol_fee_events";
    /// Target table for validated reward rows.
    pub const DB_TARGET_REWARD_EVENTS: &'static str = "k_sol_reward_events";
    /// Target table for validated admin/config rows.
    pub const DB_TARGET_POOL_ADMIN_EVENTS: &'static str = "k_sol_pool_admin_events";
    /// Target table for validated token mint rows.
    pub const DB_TARGET_TOKEN_MINT_EVENTS: &'static str = "k_sol_token_mint_events";
    /// Target table for validated token burn rows.
    pub const DB_TARGET_TOKEN_BURN_EVENTS: &'static str = "k_sol_token_burn_events";
    /// Planned target table for transfer rows.
    pub const DB_TARGET_TOKEN_TRANSFER_EVENTS: &'static str = "k_sol_token_transfer_events";
    /// Planned target table for token account lifecycle rows.
    pub const DB_TARGET_TOKEN_ACCOUNT_EVENTS: &'static str = "k_sol_token_account_events";
    /// Planned target table for orderbook rows.
    pub const DB_TARGET_ORDERBOOK_EVENTS: &'static str = "k_sol_orderbook_events";
    /// Planned target table for vault rows.
    pub const DB_TARGET_VAULT_EVENTS: &'static str = "k_sol_vault_events";
    /// Planned target table for launch and migration rows.
    pub const DB_TARGET_LAUNCH_EVENTS: &'static str = "k_sol_launch_events";
    /// Planned target table for liquidity lock rows.
    pub const DB_TARGET_LIQUIDITY_LOCK_EVENTS: &'static str = "k_sol_liquidity_lock_events";

    /// Builds a stable coverage key from source identity fields.
    pub fn build_coverage_key(
        decoder_code: &str,
        program_id: &std::option::Option<std::string::String>,
        source_repo: &std::option::Option<std::string::String>,
        source_path: &std::option::Option<std::string::String>,
        entry_kind: &str,
        entry_name: &str,
        discriminator_hex: &std::option::Option<std::string::String>,
    ) -> std::string::String {
        return format!(
            "{}|{}|{}|{}|{}|{}|{}",
            decoder_code,
            program_id.clone().unwrap_or_default(),
            source_repo.clone().unwrap_or_default(),
            source_path.clone().unwrap_or_default(),
            entry_kind,
            entry_name,
            discriminator_hex.clone().unwrap_or_default()
        );
    }

    /// Creates a new event coverage entry DTO.
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        decoder_code: std::string::String,
        program_id: std::option::Option<std::string::String>,
        program_family: std::string::String,
        surface_kind: std::string::String,
        source_repo: std::option::Option<std::string::String>,
        source_path: std::option::Option<std::string::String>,
        entry_kind: std::string::String,
        entry_name: std::string::String,
        discriminator_hex: std::option::Option<std::string::String>,
        discriminator_len: std::option::Option<i64>,
        event_family: std::option::Option<std::string::String>,
        expected_db_target: std::option::Option<std::string::String>,
        proof_status: std::string::String,
        local_event_kind: std::option::Option<std::string::String>,
        observed_count: i64,
        materialized_count: i64,
        trade_count: i64,
        first_signature: std::option::Option<std::string::String>,
        last_signature: std::option::Option<std::string::String>,
        notes: std::option::Option<std::string::String>,
    ) -> Self {
        let now = chrono::Utc::now();
        let coverage_key = Self::build_coverage_key(
            decoder_code.as_str(),
            &program_id,
            &source_repo,
            &source_path,
            entry_kind.as_str(),
            entry_name.as_str(),
            &discriminator_hex,
        );
        return Self {
            id: None,
            coverage_key,
            decoder_code,
            program_id,
            program_family,
            surface_kind,
            source_repo,
            source_path,
            entry_kind,
            entry_name,
            discriminator_hex,
            discriminator_len,
            event_family,
            expected_db_target,
            proof_status,
            local_event_kind,
            observed_count,
            materialized_count,
            trade_count,
            first_signature,
            last_signature,
            notes,
            created_at: now,
            updated_at: now,
        };
    }

    /// Creates a coverage row from an upstream registry entry without claiming local proof.
    pub fn from_upstream_registry_entry(
        entry: &crate::UpstreamRegistryEntryDto,
        event_family: std::option::Option<std::string::String>,
        expected_db_target: std::option::Option<std::string::String>,
        local_event_kind: std::option::Option<std::string::String>,
    ) -> Self {
        let discriminator_len = entry.discriminator_len.map(i64::from);
        return Self::new(
            entry.decoder_code.clone(),
            entry.program_id.clone(),
            entry.program_family.clone(),
            entry.surface_kind.clone(),
            entry.source_repo.clone(),
            entry.source_path.clone(),
            entry.entry_kind.clone(),
            entry.entry_name.clone(),
            entry.discriminator_hex.clone(),
            discriminator_len,
            event_family,
            expected_db_target,
            entry.proof_status.clone(),
            local_event_kind,
            0,
            0,
            0,
            None,
            None,
            Some(entry.notes.clone()),
        );
    }
}

impl TryFrom<crate::DexEventCoverageEntryEntity> for DexEventCoverageEntryDto {
    type Error = crate::Error;

    fn try_from(entity: crate::DexEventCoverageEntryEntity) -> Result<Self, Self::Error> {
        let created_at_result = chrono::DateTime::parse_from_rfc3339(&entity.created_at);
        let created_at = match created_at_result {
            Ok(created_at) => created_at.with_timezone(&chrono::Utc),
            Err(error) => {
                return Err(crate::Error::Db(format!(
                    "cannot parse dex event coverage created_at '{}': {}",
                    entity.created_at, error
                )));
            },
        };
        let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
        let updated_at = match updated_at_result {
            Ok(updated_at) => updated_at.with_timezone(&chrono::Utc),
            Err(error) => {
                return Err(crate::Error::Db(format!(
                    "cannot parse dex event coverage updated_at '{}': {}",
                    entity.updated_at, error
                )));
            },
        };
        return Ok(Self {
            id: Some(entity.id),
            coverage_key: entity.coverage_key,
            decoder_code: entity.decoder_code,
            program_id: entity.program_id,
            program_family: entity.program_family,
            surface_kind: entity.surface_kind,
            source_repo: entity.source_repo,
            source_path: entity.source_path,
            entry_kind: entity.entry_kind,
            entry_name: entity.entry_name,
            discriminator_hex: entity.discriminator_hex,
            discriminator_len: entity.discriminator_len,
            event_family: entity.event_family,
            expected_db_target: entity.expected_db_target,
            proof_status: entity.proof_status,
            local_event_kind: entity.local_event_kind,
            observed_count: entity.observed_count,
            materialized_count: entity.materialized_count,
            trade_count: entity.trade_count,
            first_signature: entity.first_signature,
            last_signature: entity.last_signature,
            notes: entity.notes,
            created_at,
            updated_at,
        });
    }
}

/// Application-facing event coverage summary grouped by decoder.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct DexEventCoverageSummaryDto {
    /// Internal decoder code.
    pub decoder_code: std::string::String,
    /// Number of listed coverage entries.
    pub listed_entry_count: u64,
    /// Number of entries wired to a local event kind.
    pub decoded_entry_count: u64,
    /// Number of entries observed at least once in local corpus counts.
    pub observed_entry_count: u64,
    /// Number of entries materialized at least once.
    pub materialized_entry_count: u64,
    /// Sum of local observed counts.
    pub total_observed_count: u64,
    /// Sum of local materialized counts.
    pub total_materialized_count: u64,
    /// Sum of trade counts produced by these entries.
    pub trade_count: u64,
    /// Number of decoded entries that remain audit-only.
    pub audit_only_entry_count: u64,
    /// Number of entries without an expected DB target.
    pub missing_db_target_entry_count: u64,
    /// Number of entries still upstream Git unverified.
    pub upstream_git_unverified_entry_count: u64,
    /// Number of entries mapped but not locally observed.
    pub upstream_git_mapped_unverified_entry_count: u64,
    /// Number of entries observed in local corpus.
    pub upstream_git_local_corpus_observed_entry_count: u64,
    /// Number of entries materialized from local corpus.
    pub upstream_git_local_corpus_materialized_entry_count: u64,
}

impl DexEventCoverageSummaryDto {
    fn convert_count(value: i64, field_name: &str) -> Result<u64, crate::Error> {
        let result = u64::try_from(value);
        match result {
            Ok(converted) => return Ok(converted),
            Err(error) => {
                return Err(crate::Error::Db(format!(
                    "cannot convert dex event coverage summary {} '{}' to u64: {}",
                    field_name, value, error
                )));
            },
        }
    }
}

impl TryFrom<crate::DexEventCoverageSummaryEntity> for DexEventCoverageSummaryDto {
    type Error = crate::Error;

    fn try_from(entity: crate::DexEventCoverageSummaryEntity) -> Result<Self, Self::Error> {
        let listed_entry_count =
            match Self::convert_count(entity.listed_entry_count, "listed_entry_count") {
                Ok(value) => value,
                Err(error) => return Err(error),
            };
        let decoded_entry_count =
            match Self::convert_count(entity.decoded_entry_count, "decoded_entry_count") {
                Ok(value) => value,
                Err(error) => return Err(error),
            };
        let observed_entry_count =
            match Self::convert_count(entity.observed_entry_count, "observed_entry_count") {
                Ok(value) => value,
                Err(error) => return Err(error),
            };
        let materialized_entry_count = match Self::convert_count(
            entity.materialized_entry_count,
            "materialized_entry_count",
        ) {
            Ok(value) => value,
            Err(error) => return Err(error),
        };
        let total_observed_count =
            match Self::convert_count(entity.total_observed_count, "total_observed_count") {
                Ok(value) => value,
                Err(error) => return Err(error),
            };
        let total_materialized_count = match Self::convert_count(
            entity.total_materialized_count,
            "total_materialized_count",
        ) {
            Ok(value) => value,
            Err(error) => return Err(error),
        };
        let trade_count = match Self::convert_count(entity.trade_count, "trade_count") {
            Ok(value) => value,
            Err(error) => return Err(error),
        };
        let audit_only_entry_count =
            match Self::convert_count(entity.audit_only_entry_count, "audit_only_entry_count") {
                Ok(value) => value,
                Err(error) => return Err(error),
            };
        let missing_db_target_entry_count = match Self::convert_count(
            entity.missing_db_target_entry_count,
            "missing_db_target_entry_count",
        ) {
            Ok(value) => value,
            Err(error) => return Err(error),
        };
        let upstream_git_unverified_entry_count = match Self::convert_count(
            entity.upstream_git_unverified_entry_count,
            "upstream_git_unverified_entry_count",
        ) {
            Ok(value) => value,
            Err(error) => return Err(error),
        };
        let upstream_git_mapped_unverified_entry_count = match Self::convert_count(
            entity.upstream_git_mapped_unverified_entry_count,
            "upstream_git_mapped_unverified_entry_count",
        ) {
            Ok(value) => value,
            Err(error) => return Err(error),
        };
        let upstream_git_local_corpus_observed_entry_count = match Self::convert_count(
            entity.upstream_git_local_corpus_observed_entry_count,
            "upstream_git_local_corpus_observed_entry_count",
        ) {
            Ok(value) => value,
            Err(error) => return Err(error),
        };
        let upstream_git_local_corpus_materialized_entry_count = match Self::convert_count(
            entity.upstream_git_local_corpus_materialized_entry_count,
            "upstream_git_local_corpus_materialized_entry_count",
        ) {
            Ok(value) => value,
            Err(error) => return Err(error),
        };
        return Ok(Self {
            decoder_code: entity.decoder_code,
            listed_entry_count,
            decoded_entry_count,
            observed_entry_count,
            materialized_entry_count,
            total_observed_count,
            total_materialized_count,
            trade_count,
            audit_only_entry_count,
            missing_db_target_entry_count,
            upstream_git_unverified_entry_count,
            upstream_git_mapped_unverified_entry_count,
            upstream_git_local_corpus_observed_entry_count,
            upstream_git_local_corpus_materialized_entry_count,
        });
    }
}
