use crate::models::{
    EvidenceStatus, OperabilityEvidenceCheck, QueueDiagnostics, ReliabilityEvidenceCheck,
    ReplayReport,
};

pub const fn accumulate_replay_report(total: &mut ReplayReport, report: &ReplayReport) {
    total.fetched = total.fetched.saturating_add(report.fetched);
    total.processed = total.processed.saturating_add(report.processed);
    total.done = total.done.saturating_add(report.done);
    total.dead_letter = total.dead_letter.saturating_add(report.dead_letter);
    total.requeued = total.requeued.saturating_add(report.requeued);
    total.skipped = total.skipped.saturating_add(report.skipped);
}

pub const fn checkpoint_advanced(baseline: Option<i64>, final_checkpoint: Option<i64>) -> bool {
    match (baseline, final_checkpoint) {
        (Some(before), Some(after)) => after > before,
        (None, Some(_)) => true,
        _ => false,
    }
}

pub fn build_operability_evidence_checks(
    request_logs_scanned: usize,
    traces_analyzed: usize,
    request_type_count: usize,
    queue: &QueueDiagnostics,
) -> Vec<OperabilityEvidenceCheck> {
    vec![
        OperabilityEvidenceCheck {
            name: "request_logs_present".to_string(),
            passed: request_logs_scanned > 0,
            details: format!("count={request_logs_scanned}"),
        },
        OperabilityEvidenceCheck {
            name: "trace_metrics_present".to_string(),
            passed: traces_analyzed > 0,
            details: format!(
                "traces_analyzed={traces_analyzed} request_types={request_type_count}",
            ),
        },
        OperabilityEvidenceCheck {
            name: "queue_diagnostics_collected".to_string(),
            passed: true,
            details: format!(
                "new_due={} processing={} dead_letter={}",
                queue.counts.new_due, queue.counts.processing, queue.counts.dead_letter
            ),
        },
    ]
}

pub struct ReliabilityEvidenceInput<'a> {
    pub replay_totals: &'a ReplayReport,
    pub queue_after_replay: &'a QueueDiagnostics,
    pub baseline_dead_letter: u64,
    pub final_dead_letter: u64,
    pub baseline_checkpoint: Option<i64>,
    pub final_checkpoint: Option<i64>,
    pub replay_hit_uri: Option<&'a str>,
    pub restart_hit_uri: Option<&'a str>,
}

pub fn build_reliability_evidence_checks(
    input: &ReliabilityEvidenceInput<'_>,
) -> Vec<ReliabilityEvidenceCheck> {
    let checkpoint_is_advanced =
        checkpoint_advanced(input.baseline_checkpoint, input.final_checkpoint);
    vec![
        ReliabilityEvidenceCheck {
            name: "replay_processed_events".to_string(),
            passed: input.replay_totals.done > 0,
            details: format!(
                "fetched={} processed={} done={} requeued={} dead_letter={}",
                input.replay_totals.fetched,
                input.replay_totals.processed,
                input.replay_totals.done,
                input.replay_totals.requeued,
                input.replay_totals.dead_letter
            ),
        },
        ReliabilityEvidenceCheck {
            name: "queue_drained_after_replay".to_string(),
            passed: input.queue_after_replay.counts.new_due == 0
                && input.queue_after_replay.counts.processing == 0,
            details: format!(
                "new_due={} processing={} dead_letter={}",
                input.queue_after_replay.counts.new_due,
                input.queue_after_replay.counts.processing,
                input.queue_after_replay.counts.dead_letter
            ),
        },
        ReliabilityEvidenceCheck {
            name: "dead_letter_not_increased".to_string(),
            passed: input.final_dead_letter <= input.baseline_dead_letter,
            details: format!(
                "baseline_dead_letter={} final_dead_letter={}",
                input.baseline_dead_letter, input.final_dead_letter
            ),
        },
        ReliabilityEvidenceCheck {
            name: "replay_checkpoint_advanced".to_string(),
            passed: checkpoint_is_advanced,
            details: format!(
                "baseline_checkpoint={:?} final_checkpoint={:?}",
                input.baseline_checkpoint, input.final_checkpoint
            ),
        },
        ReliabilityEvidenceCheck {
            name: "replay_checkpoint_recorded".to_string(),
            passed: input.final_checkpoint.is_some(),
            details: input.final_checkpoint.map_or_else(
                || "checkpoint missing".to_string(),
                |value| format!("checkpoint={value}"),
            ),
        },
        ReliabilityEvidenceCheck {
            name: "searchable_after_replay".to_string(),
            passed: input.replay_hit_uri.is_some(),
            details: input
                .replay_hit_uri
                .unwrap_or("no result after replay")
                .to_string(),
        },
        ReliabilityEvidenceCheck {
            name: "searchable_after_restart".to_string(),
            passed: input.restart_hit_uri.is_some(),
            details: input
                .restart_hit_uri
                .unwrap_or("no result after restart")
                .to_string(),
        },
    ]
}

pub const fn evidence_status(passed: bool) -> EvidenceStatus {
    EvidenceStatus::from_passed(passed)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::models::{OmQueueStatus, OmReflectionApplyMetrics, QueueCounts, QueueDiagnostics};

    fn queue(new_due: u64, processing: u64, dead_letter: u64) -> QueueDiagnostics {
        QueueDiagnostics {
            counts: QueueCounts {
                new_due,
                processing,
                dead_letter,
                ..QueueCounts::default()
            },
            checkpoints: Vec::new(),
            queue_dead_letter_rate: Vec::new(),
            om_status: OmQueueStatus::default(),
            om_reflection_apply_metrics: OmReflectionApplyMetrics::default(),
        }
    }

    #[test]
    fn checkpoint_advanced_handles_expected_transitions() {
        assert!(checkpoint_advanced(None, Some(1)));
        assert!(checkpoint_advanced(Some(1), Some(2)));
        assert!(!checkpoint_advanced(Some(2), Some(2)));
        assert!(!checkpoint_advanced(Some(2), Some(1)));
        assert!(!checkpoint_advanced(None, None));
    }

    #[test]
    fn operability_checks_fail_when_logs_and_traces_absent() {
        let checks = build_operability_evidence_checks(0, 0, 0, &queue(3, 1, 0));
        assert_eq!(checks.len(), 3);
        assert_eq!(checks[0].name, "request_logs_present");
        assert!(!checks[0].passed);
        assert_eq!(checks[1].name, "trace_metrics_present");
        assert!(!checks[1].passed);
        assert_eq!(checks[2].name, "queue_diagnostics_collected");
        assert!(checks[2].passed);
    }

    #[test]
    fn reliability_checks_reflect_checkpoint_and_search_failures() {
        let replay_totals = ReplayReport {
            fetched: 10,
            processed: 8,
            done: 0,
            dead_letter: 1,
            requeued: 2,
            skipped: 0,
        };
        let input = ReliabilityEvidenceInput {
            replay_totals: &replay_totals,
            queue_after_replay: &queue(1, 1, 2),
            baseline_dead_letter: 0,
            final_dead_letter: 2,
            baseline_checkpoint: Some(5),
            final_checkpoint: Some(5),
            replay_hit_uri: None,
            restart_hit_uri: None,
        };
        let checks = build_reliability_evidence_checks(&input);
        assert!(checks.iter().any(|check| !check.passed));
        assert!(
            checks
                .iter()
                .any(|check| check.name == "replay_checkpoint_advanced" && !check.passed)
        );
        assert!(
            checks
                .iter()
                .any(|check| check.name == "searchable_after_restart" && !check.passed)
        );
    }

    #[test]
    fn accumulate_replay_report_sums_fields() {
        let mut total = ReplayReport {
            fetched: 1,
            processed: 2,
            done: 3,
            dead_letter: 4,
            requeued: 5,
            skipped: 6,
        };
        let report = ReplayReport {
            fetched: 7,
            processed: 8,
            done: 9,
            dead_letter: 10,
            requeued: 11,
            skipped: 12,
        };
        accumulate_replay_report(&mut total, &report);
        assert_eq!(total.fetched, 8);
        assert_eq!(total.processed, 10);
        assert_eq!(total.done, 12);
        assert_eq!(total.dead_letter, 14);
        assert_eq!(total.requeued, 16);
        assert_eq!(total.skipped, 18);
    }

    #[test]
    fn evidence_status_maps_bool_to_expected_string() {
        assert_eq!(evidence_status(true), EvidenceStatus::Pass);
        assert_eq!(evidence_status(false), EvidenceStatus::Fail);
    }
}
