use rpki::data_model::aspa::{AspaEContent, AspaObject};
use rpki::data_model::crl::{CrlExtensions, RevokedCert, RpkixCrl};
use rpki::data_model::manifest::{FileAndHash, ManifestEContent, ManifestObject};
use rpki::data_model::rc::{
    AsResourceSet, IpResourceSet, RcExtensions, ResourceCertKind, ResourceCertificate,
    RpkixTbsCertificate, SubjectInfoAccess,
};
use rpki::data_model::roa::{RoaEContent, RoaIpAddressFamily, RoaObject};
use rpki::data_model::signed_object::{
    EncapsulatedContentInfo, ResourceEeCertificate, RpkiSignedObject, SignedAttrsProfiled,
    SignedDataProfiled, SignerInfoProfiled,
};
use rpki::data_model::ta::{TaCertificate, TrustAnchor};
use rpki::data_model::tal::Tal;
use sha2::{Digest, Sha256};

#[derive(Clone, Debug, PartialEq, Eq)]
struct BytesFmt {
    len: usize,
    sha256_hex: String,
    head_hex: String,
    tail_hex: String,
}

fn bytes_fmt(bytes: &[u8]) -> BytesFmt {
    let len = bytes.len();
    let sha = Sha256::digest(bytes);

    let head_len = len.min(16);
    let tail_len = len.min(16);

    let head = &bytes[..head_len];
    let tail = &bytes[len.saturating_sub(tail_len)..];

    BytesFmt {
        len,
        sha256_hex: hex::encode(sha),
        head_hex: hex::encode(head),
        tail_hex: hex::encode(tail),
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct TalPretty {
    raw: BytesFmt,
    comments: Vec<String>,
    ta_uris: Vec<String>,
    subject_public_key_info_der: BytesFmt,
}

impl From<&Tal> for TalPretty {
    fn from(v: &Tal) -> Self {
        Self {
            raw: bytes_fmt(&v.raw),
            comments: v.comments.clone(),
            ta_uris: v.ta_uris.iter().map(|u| u.to_string()).collect(),
            subject_public_key_info_der: bytes_fmt(&v.subject_public_key_info_der),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct TaCertificatePretty {
    raw_der: BytesFmt,
    rc_ca: ResourceCertificatePretty,
}

impl From<&TaCertificate> for TaCertificatePretty {
    fn from(v: &TaCertificate) -> Self {
        Self {
            raw_der: bytes_fmt(&v.raw_der),
            rc_ca: ResourceCertificatePretty::from(&v.rc_ca),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct TrustAnchorPretty {
    tal: TalPretty,
    ta_certificate: TaCertificatePretty,
    resolved_ta_uri: Option<String>,
}

impl From<&TrustAnchor> for TrustAnchorPretty {
    fn from(v: &TrustAnchor) -> Self {
        Self {
            tal: TalPretty::from(&v.tal),
            ta_certificate: TaCertificatePretty::from(&v.ta_certificate),
            resolved_ta_uri: v.resolved_ta_uri.as_ref().map(|u| u.to_string()),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct ResourceCertificatePretty {
    raw_der: BytesFmt,
    tbs: RpkixTbsCertificatePretty,
    kind: ResourceCertKind,
}

impl From<&ResourceCertificate> for ResourceCertificatePretty {
    fn from(v: &ResourceCertificate) -> Self {
        Self {
            raw_der: bytes_fmt(&v.raw_der),
            tbs: RpkixTbsCertificatePretty::from(&v.tbs),
            kind: v.kind,
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct RpkixTbsCertificatePretty {
    version: u32,
    serial_number: String,
    signature_algorithm: String,
    issuer_dn: String,
    subject_dn: String,
    validity_not_before: time::OffsetDateTime,
    validity_not_after: time::OffsetDateTime,
    subject_public_key_info: BytesFmt,
    extensions: RcExtensionsPretty,
}

impl From<&RpkixTbsCertificate> for RpkixTbsCertificatePretty {
    fn from(v: &RpkixTbsCertificate) -> Self {
        Self {
            version: v.version,
            serial_number: hex::encode(v.serial_number.to_bytes_be()),
            signature_algorithm: v.signature_algorithm.clone(),
            issuer_dn: v.issuer_name.to_string(),
            subject_dn: v.subject_name.to_string(),
            validity_not_before: v.validity_not_before,
            validity_not_after: v.validity_not_after,
            subject_public_key_info: bytes_fmt(&v.subject_public_key_info),
            extensions: RcExtensionsPretty::from(&v.extensions),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct RcExtensionsPretty {
    basic_constraints_ca: bool,
    subject_key_identifier: Option<BytesFmt>,
    subject_info_access: Option<SubjectInfoAccess>,
    certificate_policies_oid: Option<String>,
    ip_resources: Option<IpResourceSet>,
    as_resources: Option<AsResourceSet>,
}

impl From<&RcExtensions> for RcExtensionsPretty {
    fn from(v: &RcExtensions) -> Self {
        Self {
            basic_constraints_ca: v.basic_constraints_ca,
            subject_key_identifier: v.subject_key_identifier.as_ref().map(|b| bytes_fmt(b)),
            subject_info_access: v.subject_info_access.clone(),
            certificate_policies_oid: v.certificate_policies_oid.clone(),
            ip_resources: v.ip_resources.clone(),
            as_resources: v.as_resources.clone(),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct RpkiSignedObjectPretty {
    raw_der: BytesFmt,
    content_info_content_type: String,
    signed_data: SignedDataProfiledPretty,
}

impl From<&RpkiSignedObject> for RpkiSignedObjectPretty {
    fn from(v: &RpkiSignedObject) -> Self {
        Self {
            raw_der: bytes_fmt(&v.raw_der),
            content_info_content_type: v.content_info_content_type.clone(),
            signed_data: SignedDataProfiledPretty::from(&v.signed_data),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct SignedDataProfiledPretty {
    version: u32,
    digest_algorithms: Vec<String>,
    encap_content_info: EncapsulatedContentInfoPretty,
    certificates: Vec<ResourceEeCertificatePretty>,
    crls_present: bool,
    signer_infos: Vec<SignerInfoProfiledPretty>,
}

impl From<&SignedDataProfiled> for SignedDataProfiledPretty {
    fn from(v: &SignedDataProfiled) -> Self {
        Self {
            version: v.version,
            digest_algorithms: v.digest_algorithms.clone(),
            encap_content_info: EncapsulatedContentInfoPretty::from(&v.encap_content_info),
            certificates: v
                .certificates
                .iter()
                .map(ResourceEeCertificatePretty::from)
                .collect(),
            crls_present: v.crls_present,
            signer_infos: v
                .signer_infos
                .iter()
                .map(SignerInfoProfiledPretty::from)
                .collect(),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct EncapsulatedContentInfoPretty {
    econtent_type: String,
    econtent: BytesFmt,
}

impl From<&EncapsulatedContentInfo> for EncapsulatedContentInfoPretty {
    fn from(v: &EncapsulatedContentInfo) -> Self {
        Self {
            econtent_type: v.econtent_type.clone(),
            econtent: bytes_fmt(&v.econtent),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct ResourceEeCertificatePretty {
    raw_der: BytesFmt,
    subject_key_identifier: BytesFmt,
    spki_der: BytesFmt,
    sia_signed_object_uris: Vec<String>,
    resource_cert: ResourceCertificatePretty,
}

impl From<&ResourceEeCertificate> for ResourceEeCertificatePretty {
    fn from(v: &ResourceEeCertificate) -> Self {
        Self {
            raw_der: bytes_fmt(&v.raw_der),
            subject_key_identifier: bytes_fmt(&v.subject_key_identifier),
            spki_der: bytes_fmt(&v.spki_der),
            sia_signed_object_uris: v.sia_signed_object_uris.clone(),
            resource_cert: ResourceCertificatePretty::from(&v.resource_cert),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct SignerInfoProfiledPretty {
    version: u32,
    sid_ski: BytesFmt,
    digest_algorithm: String,
    signature_algorithm: String,
    signed_attrs: SignedAttrsProfiledPretty,
    unsigned_attrs_present: bool,
    signature: BytesFmt,
    signed_attrs_der_for_signature: BytesFmt,
}

impl From<&SignerInfoProfiled> for SignerInfoProfiledPretty {
    fn from(v: &SignerInfoProfiled) -> Self {
        Self {
            version: v.version,
            sid_ski: bytes_fmt(&v.sid_ski),
            digest_algorithm: v.digest_algorithm.clone(),
            signature_algorithm: v.signature_algorithm.clone(),
            signed_attrs: SignedAttrsProfiledPretty::from(&v.signed_attrs),
            unsigned_attrs_present: v.unsigned_attrs_present,
            signature: bytes_fmt(&v.signature),
            signed_attrs_der_for_signature: bytes_fmt(&v.signed_attrs_der_for_signature),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct SignedAttrsProfiledPretty {
    content_type: String,
    message_digest: BytesFmt,
    signing_time: rpki::data_model::common::Asn1TimeUtc,
    other_attrs_present: bool,
}

impl From<&SignedAttrsProfiled> for SignedAttrsProfiledPretty {
    fn from(v: &SignedAttrsProfiled) -> Self {
        Self {
            content_type: v.content_type.clone(),
            message_digest: bytes_fmt(&v.message_digest),
            signing_time: v.signing_time,
            other_attrs_present: v.other_attrs_present,
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct ManifestObjectPretty {
    signed_object: RpkiSignedObjectPretty,
    econtent_type: String,
    manifest: ManifestEContentPretty,
}

impl From<&ManifestObject> for ManifestObjectPretty {
    fn from(v: &ManifestObject) -> Self {
        Self {
            signed_object: RpkiSignedObjectPretty::from(&v.signed_object),
            econtent_type: v.econtent_type.clone(),
            manifest: ManifestEContentPretty::from(&v.manifest),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct ManifestEContentPretty {
    version: u32,
    manifest_number: String,
    this_update: time::OffsetDateTime,
    next_update: time::OffsetDateTime,
    file_hash_alg: String,
    files: Vec<FileAndHashPretty>,
}

impl From<&ManifestEContent> for ManifestEContentPretty {
    fn from(v: &ManifestEContent) -> Self {
        let entries = v.parse_files().expect("parse validated manifest fileList");
        Self {
            version: v.version,
            manifest_number: v.manifest_number.to_hex_upper(),
            this_update: v.this_update,
            next_update: v.next_update,
            file_hash_alg: v.file_hash_alg.clone(),
            files: entries.iter().map(FileAndHashPretty::from).collect(),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct FileAndHashPretty {
    file_name: String,
    hash_hex: String,
}

impl From<&FileAndHash> for FileAndHashPretty {
    fn from(v: &FileAndHash) -> Self {
        Self {
            file_name: v.file_name.as_str().to_string(),
            hash_hex: hex::encode(v.hash_bytes.as_ref()),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct RoaObjectPretty {
    signed_object: RpkiSignedObjectPretty,
    econtent_type: String,
    roa: RoaEContentPretty,
}

impl From<&RoaObject> for RoaObjectPretty {
    fn from(v: &RoaObject) -> Self {
        Self {
            signed_object: RpkiSignedObjectPretty::from(&v.signed_object),
            econtent_type: v.econtent_type.clone(),
            roa: RoaEContentPretty::from(&v.roa),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct RoaEContentPretty {
    version: u32,
    as_id: u32,
    ip_addr_blocks: Vec<RoaIpAddressFamily>,
}

impl From<&RoaEContent> for RoaEContentPretty {
    fn from(v: &RoaEContent) -> Self {
        Self {
            version: v.version,
            as_id: v.as_id,
            ip_addr_blocks: v.ip_addr_blocks.clone(),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct AspaObjectPretty {
    signed_object: RpkiSignedObjectPretty,
    econtent_type: String,
    aspa: AspaEContent,
}

impl From<&AspaObject> for AspaObjectPretty {
    fn from(v: &AspaObject) -> Self {
        Self {
            signed_object: RpkiSignedObjectPretty::from(&v.signed_object),
            econtent_type: v.econtent_type.clone(),
            aspa: v.aspa.clone(),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct RpkixCrlPretty {
    raw_der: BytesFmt,
    version: u32,
    issuer_dn: String,
    signature_algorithm_oid: String,
    this_update: rpki::data_model::common::Asn1TimeUtc,
    next_update: rpki::data_model::common::Asn1TimeUtc,
    revoked_certs: Vec<RevokedCert>,
    extensions: CrlExtensions,
}

impl From<&RpkixCrl> for RpkixCrlPretty {
    fn from(v: &RpkixCrl) -> Self {
        Self {
            raw_der: bytes_fmt(&v.raw_der),
            version: v.version,
            issuer_dn: v.issuer_dn.clone(),
            signature_algorithm_oid: v.signature_algorithm_oid.clone(),
            this_update: v.this_update,
            next_update: v.next_update,
            revoked_certs: v.revoked_certs.clone(),
            extensions: v.extensions.clone(),
        }
    }
}

#[test]
fn print_all_models_from_real_fixtures() {
    // Note: run this test with `cargo test --test test_model_print_real_fixtures -- --nocapture`
    // to see the output.

    let tal_path = "tests/fixtures/tal/ripe-ncc.tal";
    println!("== TAL / TA / TrustAnchor ==");
    println!("Fixture (TAL): {tal_path}");
    let tal_raw = std::fs::read(tal_path).expect("read TAL fixture");
    let tal = Tal::decode_bytes(&tal_raw).expect("decode TAL");

    let ta_path = "tests/fixtures/ta/ripe-ncc-ta.cer";
    println!("Fixture (TA): {ta_path}");
    let ta_der = std::fs::read(ta_path).expect("read TA fixture");
    let ta = TaCertificate::from_der(&ta_der).expect("parse TA cert");
    println!("TA.verify_self_signature={:?}", ta.verify_self_signature());

    let resolved = tal
        .ta_uris
        .iter()
        .find(|u| u.scheme() == "https")
        .or_else(|| tal.ta_uris.first())
        .cloned()
        .expect("tal has at least one uri");
    let trust_anchor =
        TrustAnchor::bind(tal, ta.clone(), Some(&resolved)).expect("bind trust anchor");
    println!("{:#?}", TalPretty::from(&trust_anchor.tal));
    println!("{:#?}", TaCertificatePretty::from(&ta));
    println!("{:#?}", TrustAnchorPretty::from(&trust_anchor));

    println!();
    println!("== ResourceCertificate (example non-TA CA cert) ==");
    let ca_path = "tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer";
    println!("Fixture (CA cert): {ca_path}");
    let ca_der = std::fs::read(ca_path).expect("read CA cert fixture");
    let ca_rc = ResourceCertificate::from_der(&ca_der).expect("parse CA resource certificate");
    println!("{:#?}", ResourceCertificatePretty::from(&ca_rc));

    println!();
    println!("== Signed Object / Manifest ==");
    let mft_path = "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft";
    println!("Fixture (MFT): {mft_path}");
    let mft_der = std::fs::read(mft_path).expect("read MFT fixture");
    let mft_obj = ManifestObject::decode_der(&mft_der).expect("decode manifest object");
    println!("{:#?}", ManifestObjectPretty::from(&mft_obj));
    println!(
        "Manifest.validate_embedded_ee_cert={:?}",
        mft_obj.validate_embedded_ee_cert()
    );
    println!(
        "Manifest.verify_signature={:?}",
        mft_obj.signed_object.verify_signature()
    );

    println!();
    println!("== Signed Object / ROA ==");
    let roa_path = "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa";
    println!("Fixture (ROA): {roa_path}");
    let roa_der = std::fs::read(roa_path).expect("read ROA fixture");
    let roa_obj = RoaObject::decode_der(&roa_der).expect("decode ROA object");
    println!("{:#?}", RoaObjectPretty::from(&roa_obj));
    println!(
        "ROA.validate_embedded_ee_cert={:?}",
        roa_obj.validate_embedded_ee_cert()
    );
    println!(
        "ROA.verify_signature={:?}",
        roa_obj.signed_object.verify_signature()
    );

    println!();
    println!("== Signed Object / ASPA ==");
    let aspa_path = "tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa";
    println!("Fixture (ASPA): {aspa_path}");
    let aspa_der = std::fs::read(aspa_path).expect("read ASPA fixture");
    let aspa_obj = AspaObject::decode_der(&aspa_der).expect("decode ASPA object");
    println!("{:#?}", AspaObjectPretty::from(&aspa_obj));
    println!(
        "ASPA.validate_embedded_ee_cert={:?}",
        aspa_obj.validate_embedded_ee_cert()
    );
    println!(
        "ASPA.verify_signature={:?}",
        aspa_obj.signed_object.verify_signature()
    );

    println!();
    println!("== CRL ==");
    let crl_path = "tests/fixtures/0099DEAB073EFD74C250C0A382B25012B5082AEE.crl";
    println!("Fixture (CRL): {crl_path}");
    let crl_der = std::fs::read(crl_path).expect("read CRL fixture");
    let crl = RpkixCrl::decode_der(&crl_der).expect("decode CRL");
    println!("{:#?}", RpkixCrlPretty::from(&crl));
}
