use rpki::ccr::{
    AspaPayloadSet, AspaPayloadState, CcrContentInfo, CcrDigestAlgorithm, ManifestInstance,
    ManifestState, RoaPayloadSet, RoaPayloadState, RouterKey, RouterKeySet, RouterKeyState,
    RpkiCanonicalCacheRepresentation, TrustAnchorState, compute_state_hash, decode_content_info,
    encode::{
        encode_manifest_state_payload_der, encode_router_key_state_payload_der,
        encode_trust_anchor_state_payload_der,
    },
    encode_content_info, verify_state_hash,
};
use rpki::data_model::common::BigUnsigned;
use rpki::data_model::oid::{OID_CT_RPKI_CCR, OID_CT_RPKI_CCR_RAW};

fn sample_time() -> time::OffsetDateTime {
    time::OffsetDateTime::parse(
        "2026-03-24T00:00:00Z",
        &time::format_description::well_known::Rfc3339,
    )
    .expect("valid rfc3339")
}

#[test]
fn minimal_trust_anchor_ccr_roundtrips() {
    let skis = vec![vec![0x11; 20], vec![0x22; 20]];
    let skis_der =
        encode_trust_anchor_state_payload_der(&skis).expect("encode trust anchor payload");
    let state = TrustAnchorState {
        skis,
        hash: compute_state_hash(&skis_der),
    };
    let ccr = RpkiCanonicalCacheRepresentation {
        version: 0,
        hash_alg: CcrDigestAlgorithm::Sha256,
        produced_at: sample_time(),
        mfts: None,
        vrps: None,
        vaps: None,
        tas: Some(state),
        rks: None,
    };
    let content_info = CcrContentInfo::new(ccr.clone());
    let der = encode_content_info(&content_info).expect("encode ccr");
    let decoded = decode_content_info(&der).expect("decode ccr");
    assert_eq!(decoded, content_info);
    assert_eq!(decoded.content_type_oid, OID_CT_RPKI_CCR);
}

#[test]
fn decode_rejects_wrong_content_type_oid() {
    let skis = vec![vec![0x11; 20]];
    let skis_der =
        encode_trust_anchor_state_payload_der(&skis).expect("encode trust anchor payload");
    let content_info = CcrContentInfo::new(RpkiCanonicalCacheRepresentation {
        version: 0,
        hash_alg: CcrDigestAlgorithm::Sha256,
        produced_at: sample_time(),
        mfts: None,
        vrps: None,
        vaps: None,
        tas: Some(TrustAnchorState {
            skis,
            hash: compute_state_hash(&skis_der),
        }),
        rks: None,
    });
    let mut der = encode_content_info(&content_info).expect("encode ccr");
    let needle = OID_CT_RPKI_CCR_RAW;
    let pos = der
        .windows(needle.len())
        .position(|w| w == needle)
        .expect("oid present");
    der[pos + needle.len() - 1] ^= 0x01;
    let err = decode_content_info(&der).expect_err("wrong content type must fail");
    assert!(
        err.to_string().contains("unexpected contentType OID"),
        "{err}"
    );
}

#[test]
fn ccr_requires_at_least_one_state_aspect() {
    let ccr = CcrContentInfo::new(RpkiCanonicalCacheRepresentation {
        version: 0,
        hash_alg: CcrDigestAlgorithm::Sha256,
        produced_at: sample_time(),
        mfts: None,
        vrps: None,
        vaps: None,
        tas: None,
        rks: None,
    });
    let err = encode_content_info(&ccr).expect_err("empty state aspects must fail");
    assert!(
        err.to_string()
            .contains("at least one of mfts/vrps/vaps/tas/rks")
    );
}

#[test]
fn state_hash_helpers_accept_matching_and_reject_tampered_payload() {
    let skis = vec![vec![0x11; 20]];
    let payload_der =
        encode_trust_anchor_state_payload_der(&skis).expect("encode trust anchor payload");
    let hash = compute_state_hash(&payload_der);
    assert!(verify_state_hash(&hash, &payload_der));
    let mut tampered = payload_der.clone();
    *tampered.last_mut().expect("non-empty der") ^= 0x01;
    assert!(!verify_state_hash(&hash, &tampered));
}

#[test]
fn manifest_and_router_key_skeletons_encode_payloads_and_validate_sorting() {
    let manifest_instances = vec![ManifestInstance {
        hash: vec![0x33; 32],
        size: 2048,
        aki: vec![0x44; 20],
        manifest_number: BigUnsigned {
            bytes_be: vec![0x01],
        },
        this_update: sample_time(),
        locations: vec![vec![0x30, 0x00]],
        subordinates: vec![vec![0x55; 20]],
    }];
    let mis_der = encode_manifest_state_payload_der(&manifest_instances)
        .expect("encode manifest state payload");
    let manifest_state = ManifestState {
        mis: manifest_instances,
        most_recent_update: sample_time(),
        hash: compute_state_hash(&mis_der),
    };
    manifest_state.validate().expect("manifest state validate");

    let rksets = vec![RouterKeySet {
        as_id: 64496,
        router_keys: vec![RouterKey {
            ski: vec![0x66; 20],
            spki_der: vec![0x30, 0x00],
        }],
    }];
    let rk_der = encode_router_key_state_payload_der(&rksets).expect("encode router key payload");
    let rks = RouterKeyState {
        rksets,
        hash: compute_state_hash(&rk_der),
    };
    rks.validate().expect("router key state validate");
}

fn sample_manifest_state() -> ManifestState {
    let mis = vec![ManifestInstance {
        hash: vec![0x10; 32],
        size: 2048,
        aki: vec![0x20; 20],
        manifest_number: BigUnsigned { bytes_be: vec![1] },
        this_update: sample_time(),
        locations: vec![vec![0x30, 0x00]],
        subordinates: vec![vec![0x30; 20], vec![0x40; 20]],
    }];
    let mis_der = encode_manifest_state_payload_der(&mis).expect("encode mis");
    ManifestState {
        mis,
        most_recent_update: sample_time(),
        hash: compute_state_hash(&mis_der),
    }
}

fn sample_roa_state() -> RoaPayloadState {
    let rps = vec![RoaPayloadSet {
        as_id: 64496,
        ip_addr_blocks: vec![vec![0x30, 0x00]],
    }];
    let der = rpki::ccr::encode::encode_roa_payload_state_payload_der(&rps).expect("encode rps");
    RoaPayloadState {
        rps,
        hash: compute_state_hash(&der),
    }
}

fn sample_aspa_state() -> AspaPayloadState {
    let aps = vec![AspaPayloadSet {
        customer_as_id: 64496,
        providers: vec![64497, 64498],
    }];
    let der = rpki::ccr::encode::encode_aspa_payload_state_payload_der(&aps).expect("encode aps");
    AspaPayloadState {
        aps,
        hash: compute_state_hash(&der),
    }
}

fn sample_ta_state() -> TrustAnchorState {
    let skis = vec![vec![0x11; 20], vec![0x22; 20]];
    let der = encode_trust_anchor_state_payload_der(&skis).expect("encode skis");
    TrustAnchorState {
        skis,
        hash: compute_state_hash(&der),
    }
}

fn sample_router_key_state() -> RouterKeyState {
    let rksets = vec![
        RouterKeySet {
            as_id: 64496,
            router_keys: vec![RouterKey {
                ski: vec![0x41; 20],
                spki_der: vec![0x30, 0x00],
            }],
        },
        RouterKeySet {
            as_id: 64497,
            router_keys: vec![RouterKey {
                ski: vec![0x42; 20],
                spki_der: vec![0x30, 0x00],
            }],
        },
    ];
    let der = encode_router_key_state_payload_der(&rksets).expect("encode rksets");
    RouterKeyState {
        rksets,
        hash: compute_state_hash(&der),
    }
}

#[test]
fn full_ccr_roundtrips_all_supported_state_aspects() {
    let ccr = RpkiCanonicalCacheRepresentation {
        version: 0,
        hash_alg: CcrDigestAlgorithm::Sha256,
        produced_at: sample_time(),
        mfts: Some(sample_manifest_state()),
        vrps: Some(sample_roa_state()),
        vaps: Some(sample_aspa_state()),
        tas: Some(sample_ta_state()),
        rks: Some(sample_router_key_state()),
    };
    let encoded = encode_content_info(&CcrContentInfo::new(ccr.clone())).expect("encode full ccr");
    let decoded = decode_content_info(&encoded).expect("decode full ccr");
    assert_eq!(decoded.content, ccr);
}

#[test]
fn decode_rejects_wrong_digest_algorithm_oid() {
    let ccr = CcrContentInfo::new(RpkiCanonicalCacheRepresentation {
        version: 0,
        hash_alg: CcrDigestAlgorithm::Sha256,
        produced_at: sample_time(),
        mfts: None,
        vrps: None,
        vaps: None,
        tas: Some(sample_ta_state()),
        rks: None,
    });
    let mut der = encode_content_info(&ccr).expect("encode ccr");
    let oid = rpki::data_model::oid::OID_SHA256_RAW;
    let pos = der
        .windows(oid.len())
        .position(|w| w == oid)
        .expect("sha256 oid present");
    der[pos + oid.len() - 1] ^= 0x01;
    let err = decode_content_info(&der).expect_err("decode must reject wrong digest oid");
    assert!(
        err.to_string().contains("unexpected digest algorithm OID"),
        "{err}"
    );
}

#[test]
fn decode_rejects_bad_generalized_time() {
    let ccr = CcrContentInfo::new(RpkiCanonicalCacheRepresentation {
        version: 0,
        hash_alg: CcrDigestAlgorithm::Sha256,
        produced_at: sample_time(),
        mfts: None,
        vrps: None,
        vaps: None,
        tas: Some(sample_ta_state()),
        rks: None,
    });
    let mut der = encode_content_info(&ccr).expect("encode ccr");
    let pos = der
        .windows(15)
        .position(|w| w == b"20260324000000Z")
        .expect("time present");
    der[pos + 14] = b'X';
    let err = decode_content_info(&der).expect_err("bad time must fail");
    assert!(err.to_string().contains("GeneralizedTime"), "{err}");
}

#[test]
fn manifest_state_validate_rejects_unsorted_subordinates() {
    let mut state = sample_manifest_state();
    state.mis[0].subordinates = vec![vec![0x40; 20], vec![0x30; 20]];
    let err = state
        .validate()
        .expect_err("unsorted subordinates must fail");
    assert!(err.to_string().contains("subordinates"), "{err}");
}

#[test]
fn roa_payload_state_validate_rejects_duplicate_asn_sets() {
    let state = RoaPayloadState {
        rps: vec![
            RoaPayloadSet {
                as_id: 64496,
                ip_addr_blocks: vec![vec![0x30, 0x00]],
            },
            RoaPayloadSet {
                as_id: 64496,
                ip_addr_blocks: vec![vec![0x30, 0x00]],
            },
        ],
        hash: vec![0u8; 32],
    };
    let err = state.validate().expect_err("duplicate roa sets must fail");
    assert!(err.to_string().contains("ROAPayloadState.rps"), "{err}");
}

#[test]
fn aspa_payload_state_validate_rejects_unsorted_providers() {
    let state = AspaPayloadState {
        aps: vec![AspaPayloadSet {
            customer_as_id: 64496,
            providers: vec![64498, 64497],
        }],
        hash: vec![0u8; 32],
    };
    let err = state.validate().expect_err("unsorted providers must fail");
    assert!(err.to_string().contains("providers"), "{err}");
}

#[test]
fn trust_anchor_state_validate_rejects_invalid_ski_length() {
    let state = TrustAnchorState {
        skis: vec![vec![0x11; 19]],
        hash: vec![0u8; 32],
    };
    let err = state.validate().expect_err("bad ski len must fail");
    assert!(err.to_string().contains("TrustAnchorState.skis"), "{err}");
}

#[test]
fn router_key_state_validate_rejects_unsorted_router_keys() {
    let state = RouterKeyState {
        rksets: vec![RouterKeySet {
            as_id: 64496,
            router_keys: vec![
                RouterKey {
                    ski: vec![0x42; 20],
                    spki_der: vec![0x30, 0x00],
                },
                RouterKey {
                    ski: vec![0x41; 20],
                    spki_der: vec![0x30, 0x00],
                },
            ],
        }],
        hash: vec![0u8; 32],
    };
    let err = state
        .validate()
        .expect_err("unsorted router keys must fail");
    assert!(err.to_string().contains("router_keys"), "{err}");
}

#[test]
fn manifest_instance_validate_rejects_bad_location_tag() {
    let instance = ManifestInstance {
        hash: vec![0x10; 32],
        size: 2048,
        aki: vec![0x20; 20],
        manifest_number: BigUnsigned { bytes_be: vec![1] },
        this_update: sample_time(),
        locations: vec![vec![0x04, 0x00]],
        subordinates: vec![],
    };
    let err = instance
        .validate()
        .expect_err("bad AccessDescription tag must fail");
    assert!(err.to_string().contains("unexpected tag"), "{err}");
}
