use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::str::FromStr;

use crate::data_model::resources::as_resources::Asn;
use crate::data_model::resources::ip_resources::{IPAddress, IPAddressPrefix};
use crate::rtr::payload::{Aspa, Payload, RouteOrigin, RouterKey, Ski};
use crate::slurm::file::{SlurmError, SlurmVersion};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ValidationOutputFilters {
    pub prefix_filters: Vec<PrefixFilter>,
    pub bgpsec_filters: Vec<BgpsecFilter>,
    pub aspa_filters: Vec<AspaFilter>,
}

impl ValidationOutputFilters {
    pub(crate) fn extend_from(&mut self, other: &Self) {
        self.prefix_filters
            .extend(other.prefix_filters.iter().cloned());
        self.bgpsec_filters
            .extend(other.bgpsec_filters.iter().cloned());
        self.aspa_filters.extend(other.aspa_filters.iter().cloned());
    }

    pub(crate) fn validate(&self, version: SlurmVersion) -> Result<(), SlurmError> {
        if version == SlurmVersion::V1 && !self.aspa_filters.is_empty() {
            return Err(SlurmError::Invalid(
                "slurmVersion 1 must not contain aspaFilters".to_string(),
            ));
        }

        for filter in &self.prefix_filters {
            filter.validate()?;
        }
        for filter in &self.bgpsec_filters {
            filter.validate()?;
        }
        for filter in &self.aspa_filters {
            filter.validate()?;
        }
        Ok(())
    }

    pub(crate) fn matches(&self, payload: &Payload) -> bool {
        match payload {
            Payload::RouteOrigin(route_origin) => self
                .prefix_filters
                .iter()
                .any(|filter| filter.matches(route_origin)),
            Payload::RouterKey(router_key) => self
                .bgpsec_filters
                .iter()
                .any(|filter| filter.matches(router_key)),
            Payload::Aspa(aspa) => self.aspa_filters.iter().any(|filter| filter.matches(aspa)),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LocallyAddedAssertions {
    pub prefix_assertions: Vec<PrefixAssertion>,
    pub bgpsec_assertions: Vec<BgpsecAssertion>,
    pub aspa_assertions: Vec<AspaAssertion>,
}

impl LocallyAddedAssertions {
    pub(crate) fn extend_from(&mut self, other: &Self) {
        self.prefix_assertions
            .extend(other.prefix_assertions.iter().cloned());
        self.bgpsec_assertions
            .extend(other.bgpsec_assertions.iter().cloned());
        self.aspa_assertions
            .extend(other.aspa_assertions.iter().cloned());
    }

    pub(crate) fn validate(&self, version: SlurmVersion) -> Result<(), SlurmError> {
        if version == SlurmVersion::V1 && !self.aspa_assertions.is_empty() {
            return Err(SlurmError::Invalid(
                "slurmVersion 1 must not contain aspaAssertions".to_string(),
            ));
        }

        for assertion in &self.prefix_assertions {
            assertion.validate()?;
        }
        for assertion in &self.bgpsec_assertions {
            assertion.validate()?;
        }
        for assertion in &self.aspa_assertions {
            assertion.validate()?;
        }
        Ok(())
    }

    pub(crate) fn to_payloads(&self) -> Vec<Payload> {
        let mut payloads = Vec::with_capacity(
            self.prefix_assertions.len()
                + self.bgpsec_assertions.len()
                + self.aspa_assertions.len(),
        );

        payloads.extend(
            self.prefix_assertions
                .iter()
                .cloned()
                .map(|assertion| Payload::RouteOrigin(assertion.into_route_origin())),
        );
        payloads.extend(
            self.bgpsec_assertions
                .iter()
                .cloned()
                .map(|assertion| Payload::RouterKey(assertion.into_router_key())),
        );
        payloads.extend(
            self.aspa_assertions
                .iter()
                .cloned()
                .map(|assertion| Payload::Aspa(assertion.into_aspa())),
        );

        payloads
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PrefixFilter {
    pub prefix: Option<IPAddressPrefix>,
    pub asn: Option<Asn>,
    pub comment: Option<String>,
}

impl PrefixFilter {
    fn validate(&self) -> Result<(), SlurmError> {
        if self.prefix.is_none() && self.asn.is_none() {
            return Err(SlurmError::Invalid(
                "prefixFilter must contain at least one of prefix or asn".to_string(),
            ));
        }
        Ok(())
    }

    fn matches(&self, route_origin: &RouteOrigin) -> bool {
        let prefix_match = self
            .prefix
            .is_none_or(|filter_prefix| prefix_encompasses(&filter_prefix, route_origin.prefix()));
        let asn_match = self.asn.is_none_or(|asn| asn == route_origin.asn());
        prefix_match && asn_match
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BgpsecFilter {
    pub asn: Option<Asn>,
    pub ski: Option<Ski>,
    pub comment: Option<String>,
}

impl BgpsecFilter {
    fn validate(&self) -> Result<(), SlurmError> {
        if self.asn.is_none() && self.ski.is_none() {
            return Err(SlurmError::Invalid(
                "bgpsecFilter must contain at least one of asn or SKI".to_string(),
            ));
        }
        Ok(())
    }

    fn matches(&self, router_key: &RouterKey) -> bool {
        let asn_match = self.asn.is_none_or(|asn| asn == router_key.asn());
        let ski_match = self.ski.is_none_or(|ski| ski == router_key.ski());
        asn_match && ski_match
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AspaFilter {
    pub customer_asn: Asn,
    pub comment: Option<String>,
}

impl AspaFilter {
    fn validate(&self) -> Result<(), SlurmError> {
        if self.customer_asn.into_u32() == 0 {
            return Err(SlurmError::Invalid(
                "aspaFilter customerAsn must not be AS0".to_string(),
            ));
        }
        Ok(())
    }

    fn matches(&self, aspa: &Aspa) -> bool {
        self.customer_asn == aspa.customer_asn()
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PrefixAssertion {
    pub prefix: IPAddressPrefix,
    pub asn: Asn,
    pub max_prefix_length: Option<u8>,
    pub comment: Option<String>,
}

impl PrefixAssertion {
    fn validate(&self) -> Result<(), SlurmError> {
        if self.asn.into_u32() == 0 {
            return Err(SlurmError::Invalid(
                "prefixAssertion asn must not be AS0".to_string(),
            ));
        }

        if let Some(max_prefix_length) = self.max_prefix_length {
            let address_bits = prefix_address_bits(&self.prefix);
            if max_prefix_length < self.prefix.prefix_length() {
                return Err(SlurmError::Invalid(format!(
                    "prefixAssertion maxPrefixLength {} must be >= prefix length {}",
                    max_prefix_length,
                    self.prefix.prefix_length()
                )));
            }
            if max_prefix_length > address_bits {
                return Err(SlurmError::Invalid(format!(
                    "prefixAssertion maxPrefixLength {} exceeds address size {}",
                    max_prefix_length, address_bits
                )));
            }
        }

        Ok(())
    }

    fn into_route_origin(self) -> RouteOrigin {
        RouteOrigin::new(
            self.prefix,
            self.max_prefix_length
                .unwrap_or(self.prefix.prefix_length()),
            self.asn,
        )
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BgpsecAssertion {
    pub asn: Asn,
    pub ski: Ski,
    pub router_public_key: Vec<u8>,
    pub comment: Option<String>,
}

impl BgpsecAssertion {
    fn validate(&self) -> Result<(), SlurmError> {
        RouterKey::new(self.ski, self.asn, self.router_public_key.clone())
            .validate()
            .map_err(|err| SlurmError::Invalid(err.to_string()))
    }

    fn into_router_key(self) -> RouterKey {
        RouterKey::new(self.ski, self.asn, self.router_public_key)
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AspaAssertion {
    pub customer_asn: Asn,
    pub provider_asns: Vec<Asn>,
    pub comment: Option<String>,
}

impl AspaAssertion {
    fn validate(&self) -> Result<(), SlurmError> {
        if self.provider_asns.contains(&self.customer_asn) {
            return Err(SlurmError::Invalid(
                "aspaAssertion providerAsns must not contain customerAsn".to_string(),
            ));
        }

        let providers = self
            .provider_asns
            .iter()
            .map(|asn| asn.into_u32())
            .collect::<Vec<_>>();

        if providers.windows(2).any(|window| window[0] >= window[1]) {
            return Err(SlurmError::Invalid(
                "aspaAssertion providerAsns must be strictly increasing".to_string(),
            ));
        }

        let aspa = Aspa::new(self.customer_asn, self.provider_asns.clone());
        aspa.validate_announcement()
            .map_err(|err| SlurmError::Invalid(err.to_string()))?;

        Ok(())
    }

    fn into_aspa(self) -> Aspa {
        Aspa::new(self.customer_asn, self.provider_asns)
    }
}

pub(crate) fn parse_ip_prefix(input: &str) -> Result<IPAddressPrefix, SlurmError> {
    let (addr, prefix_length) = input
        .split_once('/')
        .ok_or_else(|| SlurmError::Invalid(format!("invalid prefix '{}'", input)))?;

    let address = IpAddr::from_str(addr.trim())
        .map_err(|err| SlurmError::Invalid(format!("invalid IP address '{}': {}", addr, err)))?;
    let prefix_length = prefix_length.trim().parse::<u8>().map_err(|err| {
        SlurmError::Invalid(format!(
            "invalid prefix length '{}': {}",
            prefix_length, err
        ))
    })?;

    match address {
        IpAddr::V4(addr) => {
            if prefix_length > 32 {
                return Err(SlurmError::Invalid(format!(
                    "IPv4 prefix length {} exceeds 32",
                    prefix_length
                )));
            }
            if !is_canonical_v4(addr, prefix_length) {
                return Err(SlurmError::Invalid(format!(
                    "IPv4 prefix '{}' is not canonical",
                    input
                )));
            }
            Ok(IPAddressPrefix::new(
                IPAddress::from_ipv4(addr),
                prefix_length,
            ))
        }
        IpAddr::V6(addr) => {
            if prefix_length > 128 {
                return Err(SlurmError::Invalid(format!(
                    "IPv6 prefix length {} exceeds 128",
                    prefix_length
                )));
            }
            if !is_canonical_v6(addr, prefix_length) {
                return Err(SlurmError::Invalid(format!(
                    "IPv6 prefix '{}' is not canonical",
                    input
                )));
            }
            Ok(IPAddressPrefix::new(
                IPAddress::from_ipv6(addr),
                prefix_length,
            ))
        }
    }
}

pub(crate) fn prefix_address_bits(prefix: &IPAddressPrefix) -> u8 {
    match prefix.address() {
        IPAddress::V4(_) => 32,
        IPAddress::V6(_) => 128,
    }
}

pub(crate) fn prefix_encompasses(filter: &IPAddressPrefix, other: &IPAddressPrefix) -> bool {
    if filter.afi() != other.afi() {
        return false;
    }
    if filter.prefix_length() > other.prefix_length() {
        return false;
    }

    match (filter.address(), other.address()) {
        (IPAddress::V4(lhs), IPAddress::V4(rhs)) => {
            prefix_match_v4(lhs, rhs, filter.prefix_length())
        }
        (IPAddress::V6(lhs), IPAddress::V6(rhs)) => {
            prefix_match_v6(lhs, rhs, filter.prefix_length())
        }
        _ => false,
    }
}

fn prefix_match_v4(lhs: Ipv4Addr, rhs: Ipv4Addr, prefix_length: u8) -> bool {
    let mask = if prefix_length == 0 {
        0
    } else {
        u32::MAX << (32 - prefix_length)
    };
    (u32::from(lhs) & mask) == (u32::from(rhs) & mask)
}

fn prefix_match_v6(lhs: Ipv6Addr, rhs: Ipv6Addr, prefix_length: u8) -> bool {
    let mask = if prefix_length == 0 {
        0
    } else {
        u128::MAX << (128 - prefix_length)
    };
    (u128::from(lhs) & mask) == (u128::from(rhs) & mask)
}

fn is_canonical_v4(addr: Ipv4Addr, prefix_length: u8) -> bool {
    let mask = if prefix_length == 0 {
        0
    } else {
        u32::MAX << (32 - prefix_length)
    };
    (u32::from(addr) & !mask) == 0
}

fn is_canonical_v6(addr: Ipv6Addr, prefix_length: u8) -> bool {
    let mask = if prefix_length == 0 {
        0
    } else {
        u128::MAX << (128 - prefix_length)
    };
    (u128::from(addr) & !mask) == 0
}
