    use super::*;

    fn default_options() -> SyncOptions {
        SyncOptions {
            full: false,
            force: false,
            no_embed: false,
            no_docs: false,
            no_events: false,
            robot_mode: false,
            dry_run: false,
            issue_iids: vec![],
            mr_iids: vec![],
            project: None,
            preflight_only: false,
        }
    }

    #[test]
    fn append_failures_skips_zeroes() {
        let mut summary = "base".to_string();
        append_failures(&mut summary, &[("errors", 0), ("failures", 0)]);
        assert_eq!(summary, "base");
    }

    #[test]
    fn append_failures_renders_non_zero_counts() {
        let mut summary = "base".to_string();
        append_failures(&mut summary, &[("errors", 2), ("failures", 1)]);
        assert!(summary.contains("base"));
        assert!(summary.contains("2 errors"));
        assert!(summary.contains("1 failures"));
    }

    #[test]
    fn summarize_status_enrichment_reports_skipped_when_all_skipped() {
        let projects = vec![ProjectStatusEnrichment {
            path: "vs/typescript-code".to_string(),
            mode: "skipped".to_string(),
            reason: None,
            seen: 0,
            enriched: 0,
            cleared: 0,
            without_widget: 0,
            partial_errors: 0,
            first_partial_error: None,
            error: None,
        }];

        let (summary, has_errors) = summarize_status_enrichment(&projects);
        assert!(summary.contains("0 statuses updated"));
        assert!(summary.contains("skipped"));
        assert!(!has_errors);
    }

    #[test]
    fn summarize_status_enrichment_reports_errors() {
        let projects = vec![ProjectStatusEnrichment {
            path: "vs/typescript-code".to_string(),
            mode: "fetched".to_string(),
            reason: None,
            seen: 3,
            enriched: 1,
            cleared: 1,
            without_widget: 0,
            partial_errors: 2,
            first_partial_error: None,
            error: Some("boom".to_string()),
        }];

        let (summary, has_errors) = summarize_status_enrichment(&projects);
        assert!(summary.contains("1 statuses updated"));
        assert!(summary.contains("1 cleared"));
        assert!(summary.contains("3 seen"));
        assert!(summary.contains("3 errors"));
        assert!(has_errors);
    }

    #[test]
    fn should_print_timings_only_when_enabled_and_non_empty() {
        let stages = vec![StageTiming {
            name: "x".to_string(),
            elapsed_ms: 10,
            items_processed: 0,
            items_skipped: 0,
            errors: 0,
            rate_limit_hits: 0,
            retries: 0,
            project: None,
            sub_stages: vec![],
        }];

        assert!(should_print_timings(true, &stages));
        assert!(!should_print_timings(false, &stages));
        assert!(!should_print_timings(true, &[]));
    }

    #[test]
    fn issue_sub_rows_include_project_and_statuses() {
        let rows = issue_sub_rows(&[ProjectSummary {
            path: "vs/typescript-code".to_string(),
            items_upserted: 2,
            discussions_synced: 0,
            events_fetched: 0,
            events_failed: 0,
            statuses_enriched: 1,
            statuses_seen: 5,
            status_errors: 0,
            mr_diffs_fetched: 0,
            mr_diffs_failed: 0,
        }]);

        assert_eq!(rows.len(), 1);
        assert!(rows[0].contains("vs/typescript-code"));
        assert!(rows[0].contains("2 issues"));
        assert!(rows[0].contains("1 statuses updated"));
    }

    #[test]
    fn mr_sub_rows_include_project_and_diff_failures() {
        let rows = mr_sub_rows(&[ProjectSummary {
            path: "vs/python-code".to_string(),
            items_upserted: 3,
            discussions_synced: 0,
            events_fetched: 0,
            events_failed: 0,
            statuses_enriched: 0,
            statuses_seen: 0,
            status_errors: 0,
            mr_diffs_fetched: 4,
            mr_diffs_failed: 1,
        }]);

        assert_eq!(rows.len(), 1);
        assert!(rows[0].contains("vs/python-code"));
        assert!(rows[0].contains("3 MRs"));
        assert!(rows[0].contains("4 diffs"));
        assert!(rows[0].contains("1 diff failures"));
    }

    #[test]
    fn status_sub_rows_include_project_and_skip_reason() {
        let rows = status_sub_rows(&[ProjectStatusEnrichment {
            path: "vs/python-code".to_string(),
            mode: "skipped".to_string(),
            reason: Some("disabled".to_string()),
            seen: 0,
            enriched: 0,
            cleared: 0,
            without_widget: 0,
            partial_errors: 0,
            first_partial_error: None,
            error: None,
        }]);

        assert_eq!(rows.len(), 1);
        assert!(rows[0].contains("vs/python-code"));
        assert!(rows[0].contains("0 statuses updated"));
        assert!(rows[0].contains("skipped (disabled)"));
    }

    #[test]
    fn is_surgical_with_issues() {
        let opts = SyncOptions {
            issue_iids: vec![1],
            ..default_options()
        };
        assert!(opts.is_surgical());
    }

    #[test]
    fn is_surgical_with_mrs() {
        let opts = SyncOptions {
            mr_iids: vec![10],
            ..default_options()
        };
        assert!(opts.is_surgical());
    }

    #[test]
    fn is_surgical_empty() {
        let opts = default_options();
        assert!(!opts.is_surgical());
    }

    #[test]
    fn max_surgical_targets_is_100() {
        assert_eq!(SyncOptions::MAX_SURGICAL_TARGETS, 100);
    }

    #[test]
    fn sync_result_default_omits_surgical_fields() {
        let result = SyncResult::default();
        let json = serde_json::to_value(&result).unwrap();
        assert!(json.get("surgical_mode").is_none());
        assert!(json.get("surgical_iids").is_none());
        assert!(json.get("entity_results").is_none());
        assert!(json.get("preflight_only").is_none());
    }

    #[test]
    fn sync_result_with_surgical_fields_serializes_correctly() {
        let result = SyncResult {
            surgical_mode: Some(true),
            surgical_iids: Some(SurgicalIids {
                issues: vec![7, 42],
                merge_requests: vec![10],
            }),
            entity_results: Some(vec![
                EntitySyncResult {
                    entity_type: "issue".to_string(),
                    iid: 7,
                    outcome: "synced".to_string(),
                    error: None,
                    toctou_reason: None,
                },
                EntitySyncResult {
                    entity_type: "issue".to_string(),
                    iid: 42,
                    outcome: "skipped_toctou".to_string(),
                    error: None,
                    toctou_reason: Some("updated_at changed".to_string()),
                },
            ]),
            preflight_only: Some(false),
            ..SyncResult::default()
        };
        let json = serde_json::to_value(&result).unwrap();
        assert_eq!(json["surgical_mode"], true);
        assert_eq!(json["surgical_iids"]["issues"], serde_json::json!([7, 42]));
        assert_eq!(json["entity_results"].as_array().unwrap().len(), 2);
        assert_eq!(json["entity_results"][1]["outcome"], "skipped_toctou");
        assert_eq!(json["preflight_only"], false);
    }

    #[test]
    fn entity_sync_result_omits_none_fields() {
        let entity = EntitySyncResult {
            entity_type: "merge_request".to_string(),
            iid: 10,
            outcome: "synced".to_string(),
            error: None,
            toctou_reason: None,
        };
        let json = serde_json::to_value(&entity).unwrap();
        assert!(json.get("error").is_none());
        assert!(json.get("toctou_reason").is_none());
        assert!(json.get("entity_type").is_some());
    }

    #[test]
    fn is_surgical_with_both_issues_and_mrs() {
        let opts = SyncOptions {
            issue_iids: vec![1, 2],
            mr_iids: vec![10],
            ..default_options()
        };
        assert!(opts.is_surgical());
    }

    #[test]
    fn is_not_surgical_with_only_project() {
        let opts = SyncOptions {
            project: Some("group/repo".to_string()),
            ..default_options()
        };
        assert!(!opts.is_surgical());
    }
