use std::net::{Ipv4Addr, Ipv6Addr};

use crate::protocol::{
    ASPA_FIXED_BODY_LEN, END_OF_DATA_V0_BODY_LEN, END_OF_DATA_V1_BODY_LEN, IPV4_PREFIX_BODY_LEN,
    IPV6_PREFIX_BODY_LEN, PduHeader, PduType, ROUTER_KEY_FIXED_BODY_LEN, flag_meaning, hex_bytes,
};

pub fn print_pdu(header: &PduHeader, body: &[u8]) {
    println!("--------------------------------------------------");
    println!("PDU: {}", header.pdu_type());
    println!("version : {}", header.version);
    println!("length  : {}", header.length);

    match header.pdu_type() {
        PduType::CacheResponse => {
            println!("session_id : {}", header.session_id());
        }
        PduType::CacheReset => {
            println!("cache reset");
        }
        PduType::Ipv4Prefix => {
            print_ipv4_prefix(header, body);
        }
        PduType::Ipv6Prefix => {
            print_ipv6_prefix(header, body);
        }
        PduType::RouterKey => {
            print_router_key(header, body);
        }
        PduType::EndOfData => {
            print_end_of_data(header, body);
        }
        PduType::ErrorReport => {
            print_error_report(header, body);
        }
        PduType::SerialNotify => {
            print_serial_notify(header, body);
        }
        PduType::SerialQuery => {
            print_serial_query(header, body);
        }
        PduType::Aspa => {
            print_aspa(header, body);
        }
        PduType::ResetQuery => {
            println!("reset query");
        }
        PduType::Unknown(_) => {
            println!("field1  : {}", header.field1);
            println!("body    : {}", hex_bytes(body));
        }
    }
}

fn print_ipv4_prefix(header: &PduHeader, body: &[u8]) {
    if body.len() != IPV4_PREFIX_BODY_LEN {
        println!("invalid IPv4 Prefix body length: {}", body.len());
        println!("raw body: {}", hex_bytes(body));
        return;
    }

    let flags = body[0];
    let prefix_len = body[1];
    let max_len = body[2];
    let zero = body[3];
    let prefix = Ipv4Addr::new(body[4], body[5], body[6], body[7]);
    let asn = u32::from_be_bytes([body[8], body[9], body[10], body[11]]);

    println!("session_id : {}", header.session_id());
    println!("flags      : 0x{:02x} ({})", flags, flag_meaning(flags));
    println!("prefix_len : {}", prefix_len);
    println!("max_len    : {}", max_len);
    println!("zero       : {}", zero);
    println!("prefix     : {}", prefix);
    println!("asn        : {}", asn);
}

fn print_ipv6_prefix(header: &PduHeader, body: &[u8]) {
    if body.len() != IPV6_PREFIX_BODY_LEN {
        println!("invalid IPv6 Prefix body length: {}", body.len());
        println!("raw body: {}", hex_bytes(body));
        return;
    }

    let flags = body[0];
    let prefix_len = body[1];
    let max_len = body[2];
    let zero = body[3];

    let mut addr = [0u8; 16];
    addr.copy_from_slice(&body[4..20]);
    let prefix = Ipv6Addr::from(addr);

    let asn = u32::from_be_bytes([body[20], body[21], body[22], body[23]]);

    println!("session_id : {}", header.session_id());
    println!("flags      : 0x{:02x} ({})", flags, flag_meaning(flags));
    println!("prefix_len : {}", prefix_len);
    println!("max_len    : {}", max_len);
    println!("zero       : {}", zero);
    println!("prefix     : {}", prefix);
    println!("asn        : {}", asn);
}

fn print_end_of_data(header: &PduHeader, body: &[u8]) {
    println!("session_id : {}", header.session_id());

    match body.len() {
        END_OF_DATA_V0_BODY_LEN => {
            let serial = u32::from_be_bytes([body[0], body[1], body[2], body[3]]);
            println!("serial     : {}", serial);
            println!("variant    : v0");
        }
        END_OF_DATA_V1_BODY_LEN => {
            let serial = u32::from_be_bytes([body[0], body[1], body[2], body[3]]);
            let refresh = u32::from_be_bytes([body[4], body[5], body[6], body[7]]);
            let retry = u32::from_be_bytes([body[8], body[9], body[10], body[11]]);
            let expire = u32::from_be_bytes([body[12], body[13], body[14], body[15]]);
            println!("serial     : {}", serial);
            println!("refresh    : {}", refresh);
            println!("retry      : {}", retry);
            println!("expire     : {}", expire);
            println!("variant    : v1/v2");
        }
        _ => {
            println!("invalid EndOfData body length: {}", body.len());
            println!("raw body   : {}", hex_bytes(body));
        }
    }
}

fn print_error_report(header: &PduHeader, body: &[u8]) {
    println!(
        "error_code : {} ({})",
        header.error_code(),
        error_code_name(header.error_code())
    );

    if body.len() < 8 {
        println!("invalid ErrorReport body length: {}", body.len());
        println!("raw body   : {}", hex_bytes(body));
        return;
    }

    let encapsulated_len = u32::from_be_bytes([body[0], body[1], body[2], body[3]]) as usize;

    if body.len() < 4 + encapsulated_len + 4 {
        println!("invalid ErrorReport: truncated encapsulated PDU");
        println!("raw body   : {}", hex_bytes(body));
        return;
    }

    let encapsulated = &body[4..4 + encapsulated_len];

    let text_len_offset = 4 + encapsulated_len;
    let text_len = u32::from_be_bytes([
        body[text_len_offset],
        body[text_len_offset + 1],
        body[text_len_offset + 2],
        body[text_len_offset + 3],
    ]) as usize;

    if body.len() < text_len_offset + 4 + text_len {
        println!("invalid ErrorReport: truncated text");
        println!("raw body   : {}", hex_bytes(body));
        return;
    }

    let text_bytes = &body[text_len_offset + 4..text_len_offset + 4 + text_len];
    let text = String::from_utf8_lossy(text_bytes);

    println!("encap_len  : {}", encapsulated_len);
    if let Some(encap_header) = parse_encapsulated_header(encapsulated) {
        println!("encap_pdu_type : {}", encap_header.pdu_type());
        println!("encap_version  : {}", encap_header.version);
        println!("encap_length   : {}", encap_header.length);
        match encap_header.pdu_type() {
            PduType::ErrorReport => {
                println!("encap_field1   : error_code={}", encap_header.error_code());
            }
            PduType::Unknown(_) => {
                println!("encap_field1   : {}", encap_header.field1);
            }
            _ => {
                println!("encap_field1   : session_id={}", encap_header.session_id());
            }
        }
    } else if encapsulated_len > 0 {
        println!("encap_header   : <truncated or unavailable>");
    }
    println!("encap_pdu  : {}", hex_bytes(encapsulated));
    println!("text_len   : {}", text_len);
    println!("text_utf8  : {}", std::str::from_utf8(text_bytes).is_ok());
    println!("text       : {}", text);
}

fn print_serial_notify(header: &PduHeader, body: &[u8]) {
    if body.len() != 4 {
        println!("invalid Serial Notify body length: {}", body.len());
        println!("raw body   : {}", hex_bytes(body));
        return;
    }

    let serial = u32::from_be_bytes([body[0], body[1], body[2], body[3]]);
    println!("session_id : {}", header.session_id());
    println!("serial     : {}", serial);
}

fn print_serial_query(header: &PduHeader, body: &[u8]) {
    if body.len() != 4 {
        println!("invalid Serial Query body length: {}", body.len());
        println!("raw body   : {}", hex_bytes(body));
        return;
    }

    let serial = u32::from_be_bytes([body[0], body[1], body[2], body[3]]);
    println!("session_id : {}", header.session_id());
    println!("serial     : {}", serial);
}

fn print_router_key(_header: &PduHeader, body: &[u8]) {
    if body.len() < ROUTER_KEY_FIXED_BODY_LEN {
        println!("invalid Router Key body length: {}", body.len());
        println!("raw body   : {}", hex_bytes(body));
        return;
    }

    let flags = _header.flags();
    let zero = _header.zero();
    let ski = &body[0..20];
    let asn = u32::from_be_bytes([body[20], body[21], body[22], body[23]]);
    let spki = &body[24..];

    println!("flags      : 0x{:02x} ({})", flags, flag_meaning(flags));
    println!("zero       : {}", zero);
    println!("ski        : {}", hex_bytes(ski));
    println!("asn        : {}", asn);
    println!("spki_len   : {}", spki.len());
    println!("spki       : {}", hex_bytes(spki));

    if flags & !0x01 != 0 {
        println!("warning    : Router Key flags use reserved bits");
    }
    if zero != 0 {
        println!("warning    : Router Key reserved zero octet is non-zero");
    }
    if asn == 0 {
        println!("warning    : Router Key ASN is AS0");
    }
    if spki.is_empty() {
        println!("warning    : Router Key SPKI is empty");
    }
}

pub fn print_raw_pdu(header: &PduHeader, body: &[u8]) {
    let mut raw = Vec::with_capacity(8 + body.len());
    raw.push(header.version);
    raw.push(header.pdu_type_raw);
    raw.extend_from_slice(&header.field1.to_be_bytes());
    raw.extend_from_slice(&header.length.to_be_bytes());
    raw.extend_from_slice(body);

    println!("--------------------------------------------------");
    println!("[raw] pdu_type : {}", header.pdu_type());
    println!("[raw] octets   : {}", hex_bytes(&raw));
}

fn error_code_name(code: u16) -> &'static str {
    match code {
        0 => "Corrupt Data",
        1 => "Internal Error",
        2 => "No Data Available",
        3 => "Invalid Request",
        4 => "Unsupported Protocol Version",
        5 => "Unsupported PDU Type",
        6 => "Withdrawal of Unknown Record",
        7 => "Duplicate Announcement Received",
        8 => "Unexpected Protocol Version",
        9 => "ASPA Provider List Error",
        10 => "Transport Failure",
        11 => "Ordering Error",
        _ => "Unknown Error Code",
    }
}

fn parse_encapsulated_header(encapsulated: &[u8]) -> Option<PduHeader> {
    if encapsulated.len() < 8 {
        return None;
    }

    let mut header = [0u8; 8];
    header.copy_from_slice(&encapsulated[..8]);
    Some(PduHeader::from_bytes(header))
}

fn print_aspa(_header: &PduHeader, body: &[u8]) {
    if body.len() < ASPA_FIXED_BODY_LEN {
        println!("invalid ASPA body length: {}", body.len());
        println!("raw body   : {}", hex_bytes(body));
        return;
    }

    let flags = _header.flags();
    let zero = _header.zero();
    let customer_asn = u32::from_be_bytes([body[0], body[1], body[2], body[3]]);

    println!("flags       : 0x{:02x} ({})", flags, flag_meaning(flags));
    println!("reserved    : {}", zero);
    println!("customer_as : {}", customer_asn);

    let providers_raw = &body[4..];
    if providers_raw.len() % 4 != 0 {
        println!("invalid ASPA providers length: {}", providers_raw.len());
        println!("providers   : {}", hex_bytes(providers_raw));
        return;
    }

    let mut providers = Vec::new();
    for chunk in providers_raw.chunks_exact(4) {
        providers.push(u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
    }

    println!("providers   : {:?}", providers);

    if flags & !0x01 != 0 {
        println!("warning     : ASPA flags use reserved bits");
    }
    if zero != 0 {
        println!("warning     : ASPA reserved zero octet is non-zero");
    }
    if customer_asn == 0 {
        println!("warning     : ASPA customer ASN is AS0");
    }

    let is_announcement = flags & 0x01 == 0x01;
    if is_announcement {
        if providers.is_empty() {
            println!("warning     : ASPA announcement has empty provider list");
        }
    } else if !providers.is_empty() {
        println!("warning     : ASPA withdrawal must not contain providers");
    }

    if providers.iter().any(|asn| *asn == 0) {
        println!("warning     : ASPA provider list contains AS0");
    }

    let strictly_increasing = providers.windows(2).all(|pair| pair[0] < pair[1]);
    if !strictly_increasing && providers.len() > 1 {
        println!("warning     : ASPA providers are not in strictly increasing order");
    }
}

pub fn parse_serial_notify_serial(body: &[u8]) -> Option<u32> {
    if body.len() != 4 {
        return None;
    }

    Some(u32::from_be_bytes([body[0], body[1], body[2], body[3]]))
}

pub fn parse_end_of_data_serial(body: &[u8]) -> Option<u32> {
    match body.len() {
        4 | 16 => Some(u32::from_be_bytes([body[0], body[1], body[2], body[3]])),
        _ => None,
    }
}

#[derive(Debug, Clone, Copy)]
pub struct EndOfDataInfo {
    pub serial: u32,
    pub refresh: Option<u32>,
    pub retry: Option<u32>,
    pub expire: Option<u32>,
}

pub fn parse_end_of_data_info(body: &[u8]) -> Option<EndOfDataInfo> {
    match body.len() {
        4 => {
            let serial = u32::from_be_bytes([body[0], body[1], body[2], body[3]]);
            Some(EndOfDataInfo {
                serial,
                refresh: None,
                retry: None,
                expire: None,
            })
        }
        16 => {
            let serial = u32::from_be_bytes([body[0], body[1], body[2], body[3]]);
            let refresh = u32::from_be_bytes([body[4], body[5], body[6], body[7]]);
            let retry = u32::from_be_bytes([body[8], body[9], body[10], body[11]]);
            let expire = u32::from_be_bytes([body[12], body[13], body[14], body[15]]);
            Some(EndOfDataInfo {
                serial,
                refresh: Some(refresh),
                retry: Some(retry),
                expire: Some(expire),
            })
        }
        _ => None,
    }
}
