use std::fs;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::path::{Path, PathBuf};

use anyhow::{Context, Result, anyhow};
use der_parser::ber::{BerObject, BerObjectContent};
use der_parser::der::parse_der;

use crate::data_model::resources::as_resources::Asn;
use crate::data_model::resources::ip_resources::{IPAddress, IPAddressPrefix};
use crate::rtr::payload::{Aspa, Payload, RouteOrigin};

const VRPS_INDEX: usize = 3;
const VAPS_INDEX: usize = 4;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParsedVrp {
    pub prefix_addr: IpAddr,
    pub prefix_len: u8,
    pub max_len: u8,
    pub asn: u32,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParsedAspa {
    pub customer_asn: u32,
    pub provider_asns: Vec<u32>,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ParsedCcrSnapshot {
    pub content_type_oid: String,
    pub produced_at: Option<String>,
    pub vrps: Vec<ParsedVrp>,
    pub vaps: Vec<ParsedAspa>,
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct CcrPayloadConversion {
    pub payloads: Vec<Payload>,
    pub invalid_vrps: Vec<String>,
    pub invalid_vaps: Vec<String>,
}

pub fn load_ccr_snapshot_from_file(path: impl AsRef<Path>) -> Result<ParsedCcrSnapshot> {
    let path = path.as_ref();
    let bytes =
        fs::read(path).with_context(|| format!("failed to read CCR file: {}", path.display()))?;
    parse_ccr_bytes(&bytes).with_context(|| format!("failed to parse CCR file: {}", path.display()))
}

pub fn load_ccr_payloads_from_file(path: impl AsRef<Path>) -> Result<Vec<Payload>> {
    let snapshot = load_ccr_snapshot_from_file(path)?;
    snapshot_to_payloads(&snapshot)
}

pub fn load_ccr_payloads_from_file_with_options(
    path: impl AsRef<Path>,
    strict: bool,
) -> Result<CcrPayloadConversion> {
    let snapshot = load_ccr_snapshot_from_file(path)?;
    snapshot_to_payloads_with_options(&snapshot, strict)
}

pub fn find_latest_ccr_file(dir: impl AsRef<Path>) -> Result<PathBuf> {
    let dir = dir.as_ref();
    let latest_date_dir = find_latest_subdir_with_ccr_by_name(dir)?;
    let scan_dir = latest_date_dir.as_deref().unwrap_or(dir);
    let mut latest: Option<PathBuf> = None;

    for entry in fs::read_dir(scan_dir)
        .with_context(|| format!("failed to read CCR directory: {}", scan_dir.display()))?
    {
        let entry = entry
            .with_context(|| format!("failed to iterate CCR directory: {}", scan_dir.display()))?;
        let path = entry.path();
        if !path.is_file() {
            continue;
        }
        if path.extension().and_then(|ext| ext.to_str()) != Some("ccr") {
            continue;
        }

        if latest
            .as_ref()
            .is_none_or(|current| file_name_key(&path) > file_name_key(current))
        {
            latest = Some(path);
        }
    }

    latest.ok_or_else(|| anyhow!("no .ccr files found in {}", scan_dir.display()))
}

pub fn snapshot_to_payloads(snapshot: &ParsedCcrSnapshot) -> Result<Vec<Payload>> {
    Ok(snapshot_to_payloads_with_options(snapshot, true)?.payloads)
}

pub fn snapshot_to_payloads_with_options(
    snapshot: &ParsedCcrSnapshot,
    strict: bool,
) -> Result<CcrPayloadConversion> {
    let mut payloads = Vec::with_capacity(snapshot.vrps.len() + snapshot.vaps.len());
    let mut invalid_vrps = Vec::new();
    let mut invalid_vaps = Vec::new();

    for vrp in &snapshot.vrps {
        match build_route_origin(vrp.clone()) {
            Ok(origin) => payloads.push(Payload::RouteOrigin(origin)),
            Err(err) => {
                let msg = format!("invalid CCR VRP: {:?}: {}", vrp, err);
                if strict {
                    return Err(anyhow!(msg));
                }
                invalid_vrps.push(msg);
            }
        }
    }

    for vap in &snapshot.vaps {
        match build_aspa(vap.clone()) {
            Ok(aspa) => payloads.push(Payload::Aspa(aspa)),
            Err(err) => {
                let msg = format!("invalid CCR VAP/ASPA: {:?}: {}", vap, err);
                if strict {
                    return Err(anyhow!(msg));
                }
                invalid_vaps.push(msg);
            }
        }
    }

    Ok(CcrPayloadConversion {
        payloads,
        invalid_vrps,
        invalid_vaps,
    })
}

pub fn parse_ccr_bytes(bytes: &[u8]) -> Result<ParsedCcrSnapshot> {
    let (rem, root) = parse_der(bytes).map_err(|err| anyhow!("failed to parse CCR DER: {err}"))?;
    if !rem.is_empty() {
        return Err(anyhow!("CCR DER has {} trailing bytes", rem.len()));
    }

    let root_items = sequence_items(&root)?;
    if root_items.len() != 2 {
        return Err(anyhow!("CCR root must contain exactly 2 items"));
    }

    let content_type_oid = match &root_items[0].content {
        BerObjectContent::OID(oid) => oid.to_string(),
        other => {
            return Err(anyhow!(
                "CCR root first element must be content type OID, got {other:?}"
            ));
        }
    };

    let payload = decode_context_wrapped_sequence(&root_items[1])?;
    let payload_items = sequence_items(&payload)?;

    let produced_at = payload_items.get(1).and_then(|obj| match &obj.content {
        BerObjectContent::GeneralizedTime(t) => Some(t.to_string()),
        _ => None,
    });

    let vrps = if let Some(vrps_field) = payload_items.get(VRPS_INDEX) {
        parse_vrps(vrps_field)?
    } else {
        Vec::new()
    };

    let vaps = if let Some(vaps_field) = payload_items.get(VAPS_INDEX) {
        parse_vaps(vaps_field)?
    } else {
        Vec::new()
    };

    Ok(ParsedCcrSnapshot {
        content_type_oid,
        produced_at,
        vrps,
        vaps,
    })
}

fn parse_vrps(field: &BerObject<'_>) -> Result<Vec<ParsedVrp>> {
    let vrp_state = decode_context_wrapped_sequence(field)?;
    let vrp_state_items = sequence_items(&vrp_state)?;
    let roa_payload_sets = vrp_state_items
        .first()
        .ok_or_else(|| anyhow!("ROA payload state missing payload set list"))?;
    let roa_payload_sets = sequence_items(roa_payload_sets)?;

    let mut vrps = Vec::new();
    for payload_set in roa_payload_sets {
        let payload_set_items = sequence_items(payload_set)?;
        if payload_set_items.len() != 2 {
            return Err(anyhow!(
                "ROAPayloadSet must contain 2 items, got {}",
                payload_set_items.len()
            ));
        }

        let asn = as_u32(&payload_set_items[0], "ROAPayloadSet.asID")?;
        let families = sequence_items(&payload_set_items[1])?;

        for family in families {
            let family_items = sequence_items(family)?;
            if family_items.len() != 2 {
                return Err(anyhow!(
                    "ROAIPAddressFamily must contain 2 items, got {}",
                    family_items.len()
                ));
            }
            let address_family = as_octets(&family_items[0], "ROAIPAddressFamily.addressFamily")?;
            let addresses = sequence_items(&family_items[1])?;

            for address in addresses {
                let address_items = sequence_items(address)?;
                let (prefix_addr, prefix_len, max_len) =
                    parse_roa_address(address_family, address_items)?;
                vrps.push(ParsedVrp {
                    prefix_addr,
                    prefix_len,
                    max_len,
                    asn,
                });
            }
        }
    }

    Ok(vrps)
}

fn parse_vaps(field: &BerObject<'_>) -> Result<Vec<ParsedAspa>> {
    let vap_state = decode_context_wrapped_sequence(field)?;
    let vap_state_items = sequence_items(&vap_state)?;
    let aspa_payload_sets = vap_state_items
        .first()
        .ok_or_else(|| anyhow!("ASPA payload state missing payload set list"))?;
    let aspa_payload_sets = sequence_items(aspa_payload_sets)?;

    let mut vaps = Vec::new();
    for payload_set in aspa_payload_sets {
        let payload_set_items = sequence_items(payload_set)?;
        if payload_set_items.len() != 2 {
            return Err(anyhow!(
                "ASPAPayloadSet must contain 2 items, got {}",
                payload_set_items.len()
            ));
        }

        let customer_asn = as_u32(&payload_set_items[0], "ASPAPayloadSet.customerASID")?;
        let provider_set = sequence_items(&payload_set_items[1])?;
        let mut provider_asns = Vec::with_capacity(provider_set.len());
        for provider in provider_set {
            provider_asns.push(as_u32(provider, "ASPAPayloadSet.providerASID")?);
        }

        vaps.push(ParsedAspa {
            customer_asn,
            provider_asns,
        });
    }

    Ok(vaps)
}

fn parse_roa_address(address_family: &[u8], items: &[BerObject<'_>]) -> Result<(IpAddr, u8, u8)> {
    let address = items
        .first()
        .ok_or_else(|| anyhow!("ROAIPAddress missing address field"))?;
    let (unused_bits, bit_string) = match &address.content {
        BerObjectContent::BitString(unused_bits, bit_string) => (*unused_bits, bit_string),
        other => {
            return Err(anyhow!(
                "ROAIPAddress.address must be BIT STRING, got {other:?}"
            ));
        }
    };

    let prefix_len = (bit_string.data.len() * 8)
        .checked_sub(usize::from(unused_bits))
        .ok_or_else(|| anyhow!("invalid ROAIPAddress BIT STRING length"))?;
    let prefix_len = u8::try_from(prefix_len)
        .map_err(|_| anyhow!("prefix length {prefix_len} does not fit in u8"))?;

    let max_len = match items.get(1) {
        Some(value) => {
            let max_len = as_u32(value, "ROAIPAddress.maxLength")?;
            u8::try_from(max_len).map_err(|_| anyhow!("maxLength {max_len} does not fit in u8"))?
        }
        None => prefix_len,
    };

    let prefix_addr = match address_family {
        [0, 1] | [0, 1, ..] => {
            let mut octets = [0u8; 4];
            if bit_string.data.len() > octets.len() {
                return Err(anyhow!(
                    "IPv4 ROAIPAddress too long: {} bytes",
                    bit_string.data.len()
                ));
            }
            octets[..bit_string.data.len()].copy_from_slice(bit_string.data);
            IpAddr::V4(Ipv4Addr::from(octets))
        }
        [0, 2] | [0, 2, ..] => {
            let mut octets = [0u8; 16];
            if bit_string.data.len() > octets.len() {
                return Err(anyhow!(
                    "IPv6 ROAIPAddress too long: {} bytes",
                    bit_string.data.len()
                ));
            }
            octets[..bit_string.data.len()].copy_from_slice(bit_string.data);
            IpAddr::V6(Ipv6Addr::from(octets))
        }
        _ => {
            return Err(anyhow!(
                "unsupported ROA address family octets: {:?}",
                address_family
            ));
        }
    };

    Ok((prefix_addr, prefix_len, max_len))
}

fn sequence_items<'a>(obj: &'a BerObject<'a>) -> Result<&'a [BerObject<'a>]> {
    match &obj.content {
        BerObjectContent::Sequence(items) => Ok(items),
        other => Err(anyhow!("expected SEQUENCE, got {other:?}")),
    }
}

fn decode_context_wrapped_sequence<'a>(obj: &'a BerObject<'a>) -> Result<BerObject<'a>> {
    match &obj.content {
        BerObjectContent::Unknown(any) => {
            let (rem, inner) = parse_der(any.data)
                .map_err(|err| anyhow!("failed to parse encapsulated DER: {err}"))?;
            if !rem.is_empty() {
                return Err(anyhow!("encapsulated DER has {} trailing bytes", rem.len()));
            }
            Ok(inner)
        }
        other => Err(anyhow!(
            "expected context-specific wrapped field, got {other:?}"
        )),
    }
}

fn as_u32(obj: &BerObject<'_>, field_name: &str) -> Result<u32> {
    obj.as_u32()
        .map_err(|err| anyhow!("{field_name} must be INTEGER fitting in u32: {err}"))
}

fn as_octets<'a>(obj: &'a BerObject<'a>, field_name: &str) -> Result<&'a [u8]> {
    match &obj.content {
        BerObjectContent::OctetString(bytes) => Ok(bytes),
        other => Err(anyhow!("{field_name} must be OCTET STRING, got {other:?}")),
    }
}

fn file_name_key(path: &Path) -> String {
    path.file_name()
        .and_then(|name| name.to_str())
        .map(|name| name.to_ascii_lowercase())
        .unwrap_or_default()
}

fn find_latest_subdir_with_ccr_by_name(dir: &Path) -> Result<Option<PathBuf>> {
    let mut latest: Option<PathBuf> = None;

    for entry in
        fs::read_dir(dir).with_context(|| format!("failed to read CCR directory: {}", dir.display()))?
    {
        let entry =
            entry.with_context(|| format!("failed to iterate CCR directory: {}", dir.display()))?;
        let path = entry.path();
        if !path.is_dir() {
            continue;
        }
        if !contains_ccr_file(&path)? {
            continue;
        }

        if latest
            .as_ref()
            .is_none_or(|current| file_name_key(&path) > file_name_key(current))
        {
            latest = Some(path);
        }
    }

    Ok(latest)
}

fn contains_ccr_file(dir: &Path) -> Result<bool> {
    for entry in
        fs::read_dir(dir).with_context(|| format!("failed to read CCR directory: {}", dir.display()))?
    {
        let entry =
            entry.with_context(|| format!("failed to iterate CCR directory: {}", dir.display()))?;
        let path = entry.path();
        if path.is_file() && path.extension().and_then(|ext| ext.to_str()) == Some("ccr") {
            return Ok(true);
        }
    }

    Ok(false)
}

fn build_route_origin(vrp: ParsedVrp) -> Result<RouteOrigin> {
    let address = match vrp.prefix_addr {
        IpAddr::V4(addr) => IPAddress::from_ipv4(addr),
        IpAddr::V6(addr) => IPAddress::from_ipv6(addr),
    };

    let prefix = IPAddressPrefix::new(address, vrp.prefix_len);
    let asn = Asn::from(vrp.asn);

    Ok(RouteOrigin::new(prefix, vrp.max_len, asn))
}

fn build_aspa(aspa: ParsedAspa) -> Result<Aspa> {
    let customer_asn = Asn::from(aspa.customer_asn);
    let provider_asns = aspa
        .provider_asns
        .into_iter()
        .map(Asn::from)
        .collect::<Vec<_>>();
    let aspa = Aspa::new(customer_asn, provider_asns);
    aspa.validate_announcement()?;
    Ok(aspa)
}
