use std::collections::BTreeSet;
use std::io::Write;
use std::path::Path;

use crate::ccr::{CcrContentInfo, extract_vrp_rows};
use crate::validation::objects::{AspaAttestation, Vrp};

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct VrpCompareRow {
    pub asn: String,
    pub ip_prefix: String,
    pub max_length: String,
    pub trust_anchor: String,
}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct VapCompareRow {
    pub customer_asn: String,
    pub providers: String,
    pub trust_anchor: String,
}

fn normalize_asn(asn: u32) -> String {
    format!("AS{asn}")
}

pub fn canonical_vrp_prefix(prefix: &crate::data_model::roa::IpPrefix) -> String {
    let mut addr = prefix.addr_bytes().to_vec();
    let total_bits = match prefix.afi {
        crate::data_model::roa::RoaAfi::Ipv4 => 32usize,
        crate::data_model::roa::RoaAfi::Ipv6 => 128usize,
    };
    let keep = usize::from(prefix.prefix_len);
    for bit in keep..total_bits {
        let byte = bit / 8;
        let offset = 7 - (bit % 8);
        addr[byte] &= !(1u8 << offset);
    }
    match prefix.afi {
        crate::data_model::roa::RoaAfi::Ipv4 => {
            let ipv4 = std::net::Ipv4Addr::new(addr[0], addr[1], addr[2], addr[3]);
            format!("{ipv4}/{}", prefix.prefix_len)
        }
        crate::data_model::roa::RoaAfi::Ipv6 => {
            let mut bytes = [0u8; 16];
            bytes.copy_from_slice(&addr[..16]);
            let ipv6 = std::net::Ipv6Addr::from(bytes);
            format!("{ipv6}/{}", prefix.prefix_len)
        }
    }
}

pub fn build_vrp_compare_rows(vrps: &[Vrp]) -> BTreeSet<VrpCompareRow> {
    vrps.iter()
        .map(|vrp| VrpCompareRow {
            asn: normalize_asn(vrp.asn),
            ip_prefix: canonical_vrp_prefix(&vrp.prefix),
            max_length: vrp.max_length.to_string(),
            trust_anchor: "unknown".to_string(),
        })
        .collect()
}

pub fn build_vap_compare_rows(aspas: &[AspaAttestation]) -> BTreeSet<VapCompareRow> {
    aspas
        .iter()
        .map(|aspa| {
            let mut providers = aspa.provider_as_ids.iter().copied().collect::<Vec<_>>();
            providers.sort_unstable();
            providers.dedup();
            VapCompareRow {
                customer_asn: normalize_asn(aspa.customer_as_id),
                providers: providers
                    .into_iter()
                    .map(normalize_asn)
                    .collect::<Vec<_>>()
                    .join(";"),
                trust_anchor: "unknown".to_string(),
            }
        })
        .collect()
}

pub fn decode_ccr_compare_views(
    content_info: &CcrContentInfo,
) -> Result<(BTreeSet<VrpCompareRow>, BTreeSet<VapCompareRow>), String> {
    let vrps = extract_vrp_rows(content_info)
        .map_err(|e| format!("extract vrp rows from ccr failed: {e}"))?
        .into_iter()
        .map(|(asn, prefix, max_length)| VrpCompareRow {
            asn: normalize_asn(asn),
            ip_prefix: prefix,
            max_length: max_length.to_string(),
            trust_anchor: "unknown".to_string(),
        })
        .collect::<BTreeSet<_>>();

    let vaps = content_info
        .content
        .vaps
        .as_ref()
        .map(|state| {
            state
                .aps
                .iter()
                .map(|vap| VapCompareRow {
                    customer_asn: normalize_asn(vap.customer_as_id),
                    providers: vap
                        .providers
                        .iter()
                        .copied()
                        .map(normalize_asn)
                        .collect::<Vec<_>>()
                        .join(";"),
                    trust_anchor: "unknown".to_string(),
                })
                .collect::<BTreeSet<_>>()
        })
        .unwrap_or_default();

    Ok((vrps, vaps))
}

pub fn write_vrp_csv(path: &Path, rows: &BTreeSet<VrpCompareRow>) -> Result<(), String> {
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)
            .map_err(|e| format!("create parent dirs failed: {}: {e}", parent.display()))?;
    }
    let mut file = std::io::BufWriter::new(
        std::fs::File::create(path)
            .map_err(|e| format!("create file failed: {}: {e}", path.display()))?,
    );
    writeln!(file, "ASN,IP Prefix,Max Length,Trust Anchor").map_err(|e| e.to_string())?;
    for row in rows {
        writeln!(
            file,
            "{},{},{},{}",
            row.asn, row.ip_prefix, row.max_length, row.trust_anchor
        )
        .map_err(|e| e.to_string())?;
    }
    Ok(())
}

pub fn write_vap_csv(path: &Path, rows: &BTreeSet<VapCompareRow>) -> Result<(), String> {
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)
            .map_err(|e| format!("create parent dirs failed: {}: {e}", parent.display()))?;
    }
    let mut file = std::io::BufWriter::new(
        std::fs::File::create(path)
            .map_err(|e| format!("create file failed: {}: {e}", path.display()))?,
    );
    writeln!(file, "Customer ASN,Providers,Trust Anchor").map_err(|e| e.to_string())?;
    for row in rows {
        writeln!(
            file,
            "{},{},{}",
            row.customer_asn, row.providers, row.trust_anchor
        )
        .map_err(|e| e.to_string())?;
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ccr::{
        CcrContentInfo, CcrDigestAlgorithm, RpkiCanonicalCacheRepresentation,
        build_aspa_payload_state, build_roa_payload_state,
    };
    use crate::data_model::roa::{IpPrefix, RoaAfi};

    #[test]
    fn build_vap_compare_rows_sorts_and_dedups_providers() {
        let rows = build_vap_compare_rows(&[AspaAttestation {
            customer_as_id: 64496,
            provider_as_ids: vec![64498, 64497, 64498],
        }]);
        let row = rows.iter().next().expect("one row");
        assert_eq!(row.customer_asn, "AS64496");
        assert_eq!(row.providers, "AS64497;AS64498");
        assert_eq!(row.trust_anchor, "unknown");
    }

    #[test]
    fn decode_ccr_compare_views_extracts_vrps_and_vaps() {
        let vrps = build_roa_payload_state(&[Vrp {
            asn: 64496,
            prefix: IpPrefix {
                afi: RoaAfi::Ipv4,
                prefix_len: 24,
                addr: [192, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            },
            max_length: 24,
        }])
        .expect("build vrps");
        let vaps = build_aspa_payload_state(&[AspaAttestation {
            customer_as_id: 64496,
            provider_as_ids: vec![64497],
        }])
        .expect("build vaps");
        let content = CcrContentInfo::new(RpkiCanonicalCacheRepresentation {
            version: 0,
            hash_alg: CcrDigestAlgorithm::Sha256,
            produced_at: time::OffsetDateTime::now_utc(),
            mfts: None,
            vrps: Some(vrps),
            vaps: Some(vaps),
            tas: None,
            rks: None,
        });
        let (vrp_rows, vap_rows) =
            decode_ccr_compare_views(&content).expect("decode compare views");
        assert_eq!(vrp_rows.len(), 1);
        assert_eq!(vap_rows.len(), 1);
        assert_eq!(vap_rows.iter().next().unwrap().providers, "AS64497");
    }

    #[test]
    fn build_vrp_compare_rows_canonicalizes_ipv6_prefix_text() {
        let rows = build_vrp_compare_rows(&[Vrp {
            asn: 64496,
            prefix: IpPrefix {
                afi: RoaAfi::Ipv6,
                prefix_len: 32,
                addr: [0x20, 0x01, 0x0d, 0xb8, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
            },
            max_length: 48,
        }]);
        let row = rows.iter().next().expect("row");
        assert_eq!(row.ip_prefix, "2001:db8::/32");
    }
}
