use super::*;
use crate::subnet_catalog::{
    ClassificationSource, GeographicScope, MAINNET_NETWORK, MAINNET_REGISTRY_CANISTER_ID,
    SubnetCatalogListReport, SubnetKind, SubnetSpecialization,
};
use crate::{
    nns::data_center::report::{
        NnsDataCenterListReport, NnsDataCenterRefreshReport, NnsDataCenterRow,
    },
    nns::node::report::{NnsNodeListReport, NnsNodeRefreshReport, NnsNodeRow},
    nns::node_operator::report::{
        NnsNodeOperatorListReport, NnsNodeOperatorRefreshReport, NnsNodeOperatorRow,
    },
    nns::node_provider::report::{
        NnsNodeProviderListReport, NnsNodeProviderRefreshReport, NnsNodeProviderRow,
    },
    subnet_catalog::{SubnetCatalogRefreshReport, SubnetCatalogSubnetRow},
};

#[test]
fn topology_summary_counts_existing_reports() {
    let report = topology_summary_report_from_reports(
        MAINNET_NETWORK.to_string(),
        "https://icp-api.io".to_string(),
        subnet_report_fixture(),
        node_report_fixture(),
        node_provider_report_fixture(),
        node_operator_report_fixture(),
        data_center_report_fixture(),
    );

    assert_eq!(report.schema_version, 3);
    assert_eq!(report.subnet_count, 2);
    assert_eq!(report.application_subnet_count, 1);
    assert_eq!(report.cloud_engine_subnet_count, 0);
    assert_eq!(report.system_subnet_count, 1);
    assert_eq!(report.routing_range_count, 3);
    assert_eq!(report.node_count, 3);
    assert_eq!(report.application_node_count, 2);
    assert_eq!(report.cloud_engine_node_count, 0);
    assert_eq!(report.system_node_count, 1);
    assert_eq!(report.node_provider_count, 1);
    assert_eq!(report.node_operator_count, 2);
    assert_eq!(report.data_center_count, 1);
    assert_eq!(report.nodes_with_known_node_provider_count, 2);
    assert_eq!(report.nodes_with_unknown_node_provider_count, 1);
    assert_eq!(report.nodes_with_known_node_operator_count, 2);
    assert_eq!(report.nodes_with_unknown_node_operator_count, 1);
    assert_eq!(report.nodes_with_known_data_center_count, 2);
    assert_eq!(report.nodes_with_unknown_data_center_count, 1);
    assert_eq!(report.node_operators_with_known_node_provider_count, 1);
    assert_eq!(report.node_operators_with_unknown_node_provider_count, 1);
    assert_eq!(report.node_operators_with_known_data_center_count, 1);
    assert_eq!(report.node_operators_with_unknown_data_center_count, 1);
    assert_eq!(report.registry_versions.len(), 5);
}

#[test]
fn topology_summary_text_renders_count_and_version_tables() {
    let report = topology_summary_report_from_reports(
        MAINNET_NETWORK.to_string(),
        "https://icp-api.io".to_string(),
        subnet_report_fixture(),
        node_report_fixture(),
        node_provider_report_fixture(),
        node_operator_report_fixture(),
        data_center_report_fixture(),
    );

    let text = nns_topology_summary_report_text(&report);

    assert!(text.contains("topology: ic subnets 2 nodes 3"));
    assert!(text.contains("routing_ranges"));
    assert!(text.contains("KIND"));
    assert!(text.contains("nodes -> node providers"));
    assert!(text.contains("node operators -> data centers"));
    assert!(text.contains("COVERAGE"));
    assert!(text.contains("SOURCE"));
    assert!(text.contains("subnet_catalog"));
    assert!(text.contains("\n\n"));
}

#[test]
fn topology_coverage_report_projects_summary_join_coverage() {
    let summary = topology_summary_report_from_reports(
        MAINNET_NETWORK.to_string(),
        "https://icp-api.io".to_string(),
        subnet_report_fixture(),
        node_report_fixture(),
        node_provider_report_fixture(),
        node_operator_report_fixture(),
        data_center_report_fixture(),
    );

    let report = topology_coverage_report_from_summary(summary);

    assert_eq!(report.schema_version, 1);
    assert_eq!(report.network, MAINNET_NETWORK);
    assert_eq!(report.node_count, 3);
    assert_eq!(report.node_operator_count, 2);
    assert_eq!(report.node_provider_count, 1);
    assert_eq!(report.data_center_count, 1);
    assert_eq!(report.nodes_with_known_node_provider_count, 2);
    assert_eq!(report.nodes_with_unknown_node_provider_count, 1);
    assert_eq!(report.node_operators_with_known_data_center_count, 1);
    assert_eq!(report.node_operators_with_unknown_data_center_count, 1);
}

#[test]
fn topology_coverage_text_renders_join_coverage_table() {
    let summary = topology_summary_report_from_reports(
        MAINNET_NETWORK.to_string(),
        "https://icp-api.io".to_string(),
        subnet_report_fixture(),
        node_report_fixture(),
        node_provider_report_fixture(),
        node_operator_report_fixture(),
        data_center_report_fixture(),
    );
    let report = topology_coverage_report_from_summary(summary);

    let text = nns_topology_coverage_report_text(&report);

    assert!(text.contains("FIELD"));
    assert!(text.contains("RELATION"));
    assert!(text.contains("\n\n"));
    assert!(text.contains("nodes -> node providers"));
    assert!(text.contains("node operators -> data centers"));
    assert!(text.contains("66.7%"));
}

#[test]
fn topology_versions_report_projects_summary_registry_versions() {
    let summary = topology_summary_report_from_reports(
        MAINNET_NETWORK.to_string(),
        "https://icp-api.io".to_string(),
        subnet_report_fixture(),
        node_report_fixture(),
        node_provider_report_fixture(),
        node_operator_report_fixture(),
        data_center_report_fixture(),
    );

    let report = topology_versions_report_from_summary(summary);

    assert_eq!(report.schema_version, 1);
    assert_eq!(report.network, MAINNET_NETWORK);
    assert_eq!(report.source_count, 5);
    assert_eq!(report.registry_versions[0].source, "subnet_catalog");
    assert_eq!(report.registry_versions[1].source, "nodes");
}

#[test]
fn topology_versions_text_renders_registry_version_table() {
    let summary = topology_summary_report_from_reports(
        MAINNET_NETWORK.to_string(),
        "https://icp-api.io".to_string(),
        subnet_report_fixture(),
        node_report_fixture(),
        node_provider_report_fixture(),
        node_operator_report_fixture(),
        data_center_report_fixture(),
    );
    let report = topology_versions_report_from_summary(summary);

    let text = nns_topology_versions_report_text(&report);

    assert!(text.contains("SOURCE"));
    assert!(text.contains("VERSION"));
    assert!(text.contains("subnet_catalog"));
    assert!(text.contains("node_operators"));
    assert!(text.contains("data_centers"));
}

#[test]
fn topology_health_report_flags_mixed_versions_and_unknown_joins() {
    let summary = topology_summary_report_from_reports(
        MAINNET_NETWORK.to_string(),
        "https://icp-api.io".to_string(),
        subnet_report_fixture(),
        node_report_fixture(),
        node_provider_report_fixture(),
        node_operator_report_fixture(),
        data_center_report_fixture(),
    );

    let report = topology_health_report_from_summary(summary);

    assert_eq!(report.schema_version, 1);
    assert_eq!(report.status, "attention");
    assert_eq!(report.registry_source_count, 5);
    assert_eq!(report.registry_version_min, Some(42));
    assert_eq!(report.registry_version_max, Some(46));
    assert!(!report.registry_versions_aligned);
    assert_eq!(report.stale_source_count, 0);
    assert_eq!(report.known_join_count, 8);
    assert_eq!(report.unknown_join_count, 5);
    assert_eq!(report.join_coverage, "61.5%");
}

#[test]
fn topology_health_text_renders_check_table() {
    let summary = topology_summary_report_from_reports(
        MAINNET_NETWORK.to_string(),
        "https://icp-api.io".to_string(),
        subnet_report_fixture(),
        node_report_fixture(),
        node_provider_report_fixture(),
        node_operator_report_fixture(),
        data_center_report_fixture(),
    );
    let report = topology_health_report_from_summary(summary);

    let text = nns_topology_health_report_text(&report);

    assert!(text.contains("CHECK"));
    assert!(text.contains("registry_versions"));
    assert!(text.contains("attention"));
    assert!(text.contains("5 sources span registry versions 42..46"));
    assert!(text.contains("8 known, 5 unknown (61.5%)"));
}

#[test]
fn topology_gaps_report_lists_unknown_join_subjects() {
    let report = topology_gaps_report_from_reports(
        MAINNET_NETWORK.to_string(),
        "https://icp-api.io".to_string(),
        node_report_fixture(),
        node_provider_report_fixture(),
        node_operator_report_fixture(),
        data_center_report_fixture(),
    );

    assert_eq!(report.schema_version, 1);
    assert_eq!(report.status, "attention");
    assert_eq!(report.gap_count, 5);
    assert!(report.gaps.iter().any(|gap| {
        gap.subject_kind == "node"
            && gap.subject == "node-c"
            && gap.missing_relation == "node_provider"
            && gap.referenced_id == "provider-z"
    }));
    assert!(report.gaps.iter().any(|gap| {
        gap.subject_kind == "node"
            && gap.subject == "node-c"
            && gap.missing_relation == "node_operator"
            && gap.referenced_id == "operator-z"
    }));
    assert!(report.gaps.iter().any(|gap| {
        gap.subject_kind == "node_operator"
            && gap.subject == "operator-b"
            && gap.missing_relation == "data_center"
            && gap.referenced_id == "dc-z"
    }));
}

#[test]
fn topology_gaps_text_renders_gap_or_ok_tables() {
    let report = topology_gaps_report_from_reports(
        MAINNET_NETWORK.to_string(),
        "https://icp-api.io".to_string(),
        node_report_fixture(),
        node_provider_report_fixture(),
        node_operator_report_fixture(),
        data_center_report_fixture(),
    );

    let text = nns_topology_gaps_report_text(&report);

    assert!(text.contains("SUBJECT_KIND"));
    assert!(text.contains("MISSING_RELATION"));
    assert!(text.contains("node-c"));
    assert!(text.contains("provider-z"));

    let clean_report = topology_gaps_report_from_reports(
        MAINNET_NETWORK.to_string(),
        "https://icp-api.io".to_string(),
        node_report_fixture(),
        complete_node_provider_report_fixture(),
        complete_node_operator_report_fixture(),
        complete_data_center_report_fixture(),
    );
    let clean_text = nns_topology_gaps_report_text(&clean_report);

    assert_eq!(clean_report.status, "ok");
    assert_eq!(clean_report.gap_count, 0);
    assert!(clean_text.contains("STATUS"));
    assert!(clean_text.contains("no topology join gaps"));
}

#[test]
fn topology_capacity_report_summarizes_operator_allowance() {
    let report = topology_capacity_report_from_report(
        MAINNET_NETWORK.to_string(),
        "https://icp-api.io".to_string(),
        node_operator_report_fixture(),
    );

    assert_eq!(report.schema_version, 1);
    assert_eq!(report.status, "attention");
    assert_eq!(report.node_operator_count, 2);
    assert_eq!(report.total_node_allowance, 2);
    assert_eq!(report.assigned_node_count, 3);
    assert_eq!(report.available_node_slots, 0);
    assert_eq!(report.over_assigned_operator_count, 1);
    assert_eq!(report.over_assigned_node_count, 1);
    assert!(report.capacity.iter().any(|row| {
        row.node_operator_principal == "operator-a"
            && row.assigned_node_count == Some(2)
            && row.over_assigned_node_count == Some(1)
            && row.utilization == "200.0%"
            && row.status == "over"
    }));
}

#[test]
fn topology_capacity_text_renders_operator_capacity_table() {
    let report = topology_capacity_report_from_report(
        MAINNET_NETWORK.to_string(),
        "https://icp-api.io".to_string(),
        node_operator_report_fixture(),
    );

    let text = nns_topology_capacity_report_text(&report);

    assert!(text.contains("NODE_OPERATOR"));
    assert!(text.contains("ALLOWANCE"));
    assert!(text.contains("UTILIZATION"));
    assert!(text.contains("operator-a"));
    assert!(text.contains("200.0%"));
}

#[test]
fn topology_regions_report_summarizes_data_center_regions() {
    let report = topology_regions_report_from_report(
        MAINNET_NETWORK.to_string(),
        "https://icp-api.io".to_string(),
        data_center_report_fixture(),
    );

    assert_eq!(report.schema_version, 1);
    assert_eq!(report.region_count, 1);
    assert_eq!(report.data_center_count, 1);
    assert_eq!(report.node_operator_count, 2);
    assert_eq!(report.node_provider_count, 1);
    assert_eq!(report.node_count, 3);
    assert_eq!(report.regions[0].region, "eu-west");
    assert_eq!(report.regions[0].data_center_count, 1);
}

#[test]
fn topology_regions_text_renders_region_table() {
    let report = topology_regions_report_from_report(
        MAINNET_NETWORK.to_string(),
        "https://icp-api.io".to_string(),
        data_center_report_fixture(),
    );

    let text = nns_topology_regions_report_text(&report);

    assert!(text.contains("REGION"));
    assert!(text.contains("DATA_CENTERS"));
    assert!(text.contains("NODE_OPERATORS"));
    assert!(text.contains("eu-west"));
}

#[test]
fn topology_providers_report_summarizes_provider_distribution() {
    let report = topology_providers_report_from_reports(
        MAINNET_NETWORK.to_string(),
        "https://icp-api.io".to_string(),
        node_report_fixture(),
        node_provider_report_fixture(),
        node_operator_report_fixture(),
        data_center_report_fixture(),
    );

    assert_eq!(report.schema_version, 1);
    assert_eq!(report.registered_node_provider_count, 1);
    assert_eq!(report.referenced_node_provider_count, 2);
    assert_eq!(report.provider_with_nodes_count, 2);
    assert_eq!(report.provider_with_node_operators_count, 2);
    assert_eq!(report.total_node_count, 3);
    assert_eq!(report.total_node_operator_count, 2);
    assert_eq!(report.total_node_allowance, 2);
    assert_eq!(report.over_assigned_provider_count, 1);
    assert_eq!(report.unknown_provider_count, 1);
    assert!(report.providers.iter().any(|provider| {
        provider.node_provider_principal == "provider-a"
            && provider.registered
            && provider.topology_node_count == 2
            && provider.node_operator_count == 1
            && provider.over_assigned_node_count == 1
            && provider.status == "over"
    }));
    assert!(report.providers.iter().any(|provider| {
        provider.node_provider_principal == "provider-z"
            && !provider.registered
            && provider.topology_node_count == 1
            && provider.node_operator_count == 1
            && provider.status == "unknown_provider"
    }));
}

#[test]
fn topology_providers_text_renders_provider_table() {
    let report = topology_providers_report_from_reports(
        MAINNET_NETWORK.to_string(),
        "https://icp-api.io".to_string(),
        node_report_fixture(),
        node_provider_report_fixture(),
        node_operator_report_fixture(),
        data_center_report_fixture(),
    );

    let text = nns_topology_providers_report_text(&report);

    assert!(text.contains("NODE_PROVIDER"));
    assert!(text.contains("GOV_NODES"));
    assert!(text.contains("OPERATORS"));
    assert!(text.contains("provider-a"));
    assert!(text.contains("unknown_provider"));
}

#[test]
fn topology_summary_rejects_local_network_with_topology_hint() {
    let request = NnsTopologySummaryRequest {
        icp_root: std::env::temp_dir(),
        network: "local".to_string(),
        source_endpoint: "https://icp-api.io".to_string(),
        now_unix_secs: 1_780_531_200,
    };

    let err = build_nns_topology_summary_report(&request).expect_err("local rejected");
    let message = err.to_string();

    assert!(message.contains("supports only the mainnet `ic` network"));
    assert!(message.contains("icq --network ic nns topology summary"));
    assert!(message.contains("icq --network ic nns topology coverage"));
    assert!(message.contains("icq --network ic nns topology versions"));
    assert!(message.contains("icq --network ic nns topology health"));
    assert!(message.contains("icq --network ic nns topology gaps"));
    assert!(message.contains("icq --network ic nns topology capacity"));
    assert!(message.contains("icq --network ic nns topology regions"));
    assert!(message.contains("icq --network ic nns topology providers"));
    assert!(message.contains("icq --network ic nns topology refresh"));
}

#[test]
fn topology_refresh_counts_component_reports() {
    let report = topology_refresh_report_from_reports(
        MAINNET_NETWORK.to_string(),
        "https://icp-api.io".to_string(),
        false,
        NnsTopologyRefreshComponentReports {
            subnet: subnet_refresh_report_fixture(),
            node: node_refresh_report_fixture(),
            node_provider: node_provider_refresh_report_fixture(),
            node_operator: node_operator_refresh_report_fixture(),
            data_center: data_center_refresh_report_fixture(),
        },
    );

    assert_eq!(report.schema_version, 1);
    assert_eq!(report.component_count, 5);
    assert_eq!(report.wrote_cache_count, 5);
    assert_eq!(report.replaced_existing_cache_count, 1);
    assert_eq!(report.components[0].source, "subnet_catalog");
    assert_eq!(report.components[0].item_count, 2);
    assert_eq!(report.components[1].source, "nodes");
    assert_eq!(report.components[1].item_count, 3);
}

#[test]
fn topology_refresh_text_renders_component_table() {
    let report = topology_refresh_report_from_reports(
        MAINNET_NETWORK.to_string(),
        "https://icp-api.io".to_string(),
        true,
        NnsTopologyRefreshComponentReports {
            subnet: dry_run_subnet_refresh_report_fixture(),
            node: dry_run_node_refresh_report_fixture(),
            node_provider: dry_run_node_provider_refresh_report_fixture(),
            node_operator: dry_run_node_operator_refresh_report_fixture(),
            data_center: dry_run_data_center_refresh_report_fixture(),
        },
    );

    let text = nns_topology_refresh_report_text(&report);

    assert!(text.contains("topology_refresh: ic components 5 wrote 0 replaced 1 dry_run yes"));
    assert!(text.contains("subnet_catalog"));
    assert!(text.contains("node_operators"));
    assert!(text.contains("data_centers"));
}

fn subnet_report_fixture() -> SubnetCatalogListReport {
    SubnetCatalogListReport {
        schema_version: 1,
        network: MAINNET_NETWORK.to_string(),
        catalog_path: "catalog.json".to_string(),
        catalog_schema_version: 1,
        registry_canister_id: MAINNET_REGISTRY_CANISTER_ID.to_string(),
        registry_version: 42,
        fetched_at: "2026-06-04T00:00:00Z".to_string(),
        catalog_stale: false,
        stale_reason: "fresh".to_string(),
        resolver_backend: "local-nns-subnet-catalog".to_string(),
        subnets: vec![
            subnet_row("pzp6e", SubnetKind::Application, 2, 2),
            subnet_row("tdb26", SubnetKind::System, 1, 1),
        ],
    }
}

fn subnet_refresh_report_fixture() -> SubnetCatalogRefreshReport {
    SubnetCatalogRefreshReport {
        schema_version: 1,
        network: MAINNET_NETWORK.to_string(),
        catalog_path: ".icq/subnet-catalog/ic/catalog.json".to_string(),
        refresh_lock_path: ".icq/subnet-catalog/ic/refresh.lock".to_string(),
        output_path: None,
        registry_canister_id: MAINNET_REGISTRY_CANISTER_ID.to_string(),
        registry_version: 42,
        fetched_at: "2026-06-04T00:00:00Z".to_string(),
        source_endpoint: "https://icp-api.io".to_string(),
        fetched_by: "test".to_string(),
        dry_run: false,
        wrote_catalog: true,
        replaced_existing_catalog: true,
        subnet_count: 2,
        routing_range_count: 3,
    }
}

fn dry_run_subnet_refresh_report_fixture() -> SubnetCatalogRefreshReport {
    SubnetCatalogRefreshReport {
        dry_run: true,
        wrote_catalog: false,
        ..subnet_refresh_report_fixture()
    }
}

fn node_refresh_report_fixture() -> NnsNodeRefreshReport {
    NnsNodeRefreshReport {
        schema_version: 1,
        network: MAINNET_NETWORK.to_string(),
        cache_path: ".icq/node/ic/nodes.json".to_string(),
        refresh_lock_path: ".icq/node/ic/refresh.lock".to_string(),
        output_path: None,
        registry_canister_id: MAINNET_REGISTRY_CANISTER_ID.to_string(),
        registry_version: 43,
        fetched_at: "2026-06-04T00:01:00Z".to_string(),
        source_endpoint: "https://icp-api.io".to_string(),
        fetched_by: "test".to_string(),
        dry_run: false,
        wrote_cache: true,
        replaced_existing_cache: false,
        node_count: 3,
    }
}

fn dry_run_node_refresh_report_fixture() -> NnsNodeRefreshReport {
    NnsNodeRefreshReport {
        dry_run: true,
        wrote_cache: false,
        ..node_refresh_report_fixture()
    }
}

fn node_provider_refresh_report_fixture() -> NnsNodeProviderRefreshReport {
    NnsNodeProviderRefreshReport {
        schema_version: 1,
        network: MAINNET_NETWORK.to_string(),
        cache_path: ".icq/node-provider/ic/providers.json".to_string(),
        refresh_lock_path: ".icq/node-provider/ic/refresh.lock".to_string(),
        output_path: None,
        governance_canister_id: "rrkah-fqaaa-aaaaa-aaaaq-cai".to_string(),
        registry_canister_id: MAINNET_REGISTRY_CANISTER_ID.to_string(),
        registry_version: 44,
        fetched_at: "2026-06-04T00:02:00Z".to_string(),
        source_endpoint: "https://icp-api.io".to_string(),
        fetched_by: "test".to_string(),
        dry_run: false,
        wrote_cache: true,
        replaced_existing_cache: false,
        node_provider_count: 1,
    }
}

fn dry_run_node_provider_refresh_report_fixture() -> NnsNodeProviderRefreshReport {
    NnsNodeProviderRefreshReport {
        dry_run: true,
        wrote_cache: false,
        ..node_provider_refresh_report_fixture()
    }
}

fn node_operator_refresh_report_fixture() -> NnsNodeOperatorRefreshReport {
    NnsNodeOperatorRefreshReport {
        schema_version: 1,
        network: MAINNET_NETWORK.to_string(),
        cache_path: ".icq/node-operator/ic/operators.json".to_string(),
        refresh_lock_path: ".icq/node-operator/ic/refresh.lock".to_string(),
        output_path: None,
        registry_canister_id: MAINNET_REGISTRY_CANISTER_ID.to_string(),
        registry_version: 45,
        fetched_at: "2026-06-04T00:03:00Z".to_string(),
        source_endpoint: "https://icp-api.io".to_string(),
        fetched_by: "test".to_string(),
        dry_run: false,
        wrote_cache: true,
        replaced_existing_cache: false,
        node_operator_count: 2,
    }
}

fn dry_run_node_operator_refresh_report_fixture() -> NnsNodeOperatorRefreshReport {
    NnsNodeOperatorRefreshReport {
        dry_run: true,
        wrote_cache: false,
        ..node_operator_refresh_report_fixture()
    }
}

fn data_center_refresh_report_fixture() -> NnsDataCenterRefreshReport {
    NnsDataCenterRefreshReport {
        schema_version: 1,
        network: MAINNET_NETWORK.to_string(),
        cache_path: ".icq/data-center/ic/data-centers.json".to_string(),
        refresh_lock_path: ".icq/data-center/ic/refresh.lock".to_string(),
        output_path: None,
        registry_canister_id: MAINNET_REGISTRY_CANISTER_ID.to_string(),
        registry_version: 46,
        fetched_at: "2026-06-04T00:04:00Z".to_string(),
        source_endpoint: "https://icp-api.io".to_string(),
        fetched_by: "test".to_string(),
        dry_run: false,
        wrote_cache: true,
        replaced_existing_cache: false,
        data_center_count: 1,
    }
}

fn dry_run_data_center_refresh_report_fixture() -> NnsDataCenterRefreshReport {
    NnsDataCenterRefreshReport {
        dry_run: true,
        wrote_cache: false,
        ..data_center_refresh_report_fixture()
    }
}

fn subnet_row(
    subnet_principal: &str,
    subnet_kind: SubnetKind,
    node_count: u32,
    range_count: usize,
) -> SubnetCatalogSubnetRow {
    SubnetCatalogSubnetRow {
        subnet_principal: subnet_principal.to_string(),
        subnet_kind,
        subnet_kind_source: ClassificationSource::Registry,
        subnet_specialization: SubnetSpecialization::None,
        subnet_specialization_source: ClassificationSource::Computed,
        geographic_scope: GeographicScope::Global,
        geographic_scope_source: ClassificationSource::Computed,
        subnet_label: subnet_kind.as_str().to_string(),
        subnet_label_source: ClassificationSource::Computed,
        node_count: Some(node_count),
        charges_apply_by_default: subnet_kind.charges_apply_by_default(),
        range_count,
        ranges_shown: 0,
        range_offset: 0,
        range_limit: 1,
        ranges: Vec::new(),
    }
}

fn node_report_fixture() -> NnsNodeListReport {
    NnsNodeListReport {
        schema_version: 1,
        network: MAINNET_NETWORK.to_string(),
        registry_canister_id: MAINNET_REGISTRY_CANISTER_ID.to_string(),
        registry_version: 43,
        fetched_at: "2026-06-04T00:01:00Z".to_string(),
        source_endpoint: "https://icp-api.io".to_string(),
        fetched_by: "test".to_string(),
        node_count: 3,
        nodes: vec![
            node_row("node-a", "operator-a", "provider-a", "dc1", "application"),
            node_row("node-b", "operator-a", "provider-a", "dc1", "application"),
            node_row("node-c", "operator-z", "provider-z", "dc-z", "system"),
        ],
    }
}

fn node_row(
    node_principal: &str,
    node_operator_principal: &str,
    node_provider_principal: &str,
    data_center_id: &str,
    subnet_kind: &str,
) -> NnsNodeRow {
    NnsNodeRow {
        node_principal: node_principal.to_string(),
        node_operator_principal: node_operator_principal.to_string(),
        node_provider_principal: node_provider_principal.to_string(),
        subnet_principal: "subnet-a".to_string(),
        subnet_kind: subnet_kind.to_string(),
        data_center_id: data_center_id.to_string(),
    }
}

fn node_provider_report_fixture() -> NnsNodeProviderListReport {
    NnsNodeProviderListReport {
        schema_version: 1,
        network: MAINNET_NETWORK.to_string(),
        governance_canister_id: "rrkah-fqaaa-aaaaa-aaaaq-cai".to_string(),
        registry_canister_id: MAINNET_REGISTRY_CANISTER_ID.to_string(),
        registry_version: 44,
        fetched_at: "2026-06-04T00:02:00Z".to_string(),
        source_endpoint: "https://icp-api.io".to_string(),
        fetched_by: "test".to_string(),
        node_provider_count: 1,
        node_providers: vec![NnsNodeProviderRow {
            node_provider_principal: "provider-a".to_string(),
            name: None,
            node_count: Some(3),
            reward_account_hex: None,
        }],
    }
}

fn complete_node_provider_report_fixture() -> NnsNodeProviderListReport {
    let mut report = node_provider_report_fixture();
    report.node_provider_count = 2;
    report.node_providers.push(NnsNodeProviderRow {
        node_provider_principal: "provider-z".to_string(),
        name: None,
        node_count: Some(1),
        reward_account_hex: None,
    });
    report
}

fn node_operator_report_fixture() -> NnsNodeOperatorListReport {
    NnsNodeOperatorListReport {
        schema_version: 1,
        network: MAINNET_NETWORK.to_string(),
        registry_canister_id: MAINNET_REGISTRY_CANISTER_ID.to_string(),
        registry_version: 45,
        fetched_at: "2026-06-04T00:03:00Z".to_string(),
        source_endpoint: "https://icp-api.io".to_string(),
        fetched_by: "test".to_string(),
        node_operator_count: 2,
        node_operators: vec![
            NnsNodeOperatorRow {
                node_operator_principal: "operator-a".to_string(),
                node_provider_principal: "provider-a".to_string(),
                node_allowance: 1,
                data_center_id: "dc1".to_string(),
                node_count: Some(2),
            },
            NnsNodeOperatorRow {
                node_operator_principal: "operator-b".to_string(),
                node_provider_principal: "provider-z".to_string(),
                node_allowance: 1,
                data_center_id: "dc-z".to_string(),
                node_count: Some(1),
            },
        ],
    }
}

fn complete_node_operator_report_fixture() -> NnsNodeOperatorListReport {
    let mut report = node_operator_report_fixture();
    report.node_operator_count = 3;
    report.node_operators.push(NnsNodeOperatorRow {
        node_operator_principal: "operator-z".to_string(),
        node_provider_principal: "provider-z".to_string(),
        node_allowance: 1,
        data_center_id: "dc-z".to_string(),
        node_count: Some(1),
    });
    report
}

fn data_center_report_fixture() -> NnsDataCenterListReport {
    NnsDataCenterListReport {
        schema_version: 1,
        network: MAINNET_NETWORK.to_string(),
        registry_canister_id: MAINNET_REGISTRY_CANISTER_ID.to_string(),
        registry_version: 46,
        fetched_at: "2026-06-04T00:04:00Z".to_string(),
        source_endpoint: "https://icp-api.io".to_string(),
        fetched_by: "test".to_string(),
        data_center_count: 1,
        data_centers: vec![NnsDataCenterRow {
            data_center_id: "dc1".to_string(),
            region: "eu-west".to_string(),
            owner: "example".to_string(),
            latitude: None,
            longitude: None,
            node_operator_count: 2,
            node_provider_count: 1,
            node_count: 3,
        }],
    }
}

fn complete_data_center_report_fixture() -> NnsDataCenterListReport {
    let mut report = data_center_report_fixture();
    report.data_center_count = 2;
    report.data_centers.push(NnsDataCenterRow {
        data_center_id: "dc-z".to_string(),
        region: "eu-west".to_string(),
        owner: "example".to_string(),
        latitude: None,
        longitude: None,
        node_operator_count: 1,
        node_provider_count: 1,
        node_count: 1,
    });
    report
}
