use crate::data_model::oid::{OID_AD_CA_REPOSITORY, OID_AD_RPKI_MANIFEST, OID_AD_RPKI_NOTIFY};
use crate::data_model::rc::{ResourceCertKind, ResourceCertificate, SubjectInfoAccess};

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CaInstanceUris {
    /// rsync sync base URI used for live/replay publication point fetches (must end with `/`).
    ///
    /// This is the parent directory of `manifest_rsync_uri`, not necessarily the full
    /// `id-ad-caRepository` URI. Using the manifest parent keeps sync scope narrow while
    /// `publication_point_rsync_uri` preserves the full RFC 9286 publication-point value.
    pub rsync_base_uri: String,
    /// rsync URI for the manifest object (`.mft`).
    pub manifest_rsync_uri: String,
    /// Publication point rsync URI (RFC 9286 terminology).
    pub publication_point_rsync_uri: String,
    /// Optional RRDP notification URI (https).
    pub rrdp_notification_uri: Option<String>,
}

#[derive(Debug, thiserror::Error)]
pub enum CaInstanceUrisError {
    #[error("certificate must be a CA certificate (RFC 6487 §4.8.1)")]
    NotCa,

    #[error(
        "CA certificate must contain Subject Information Access extension (RFC 6487 §4.8.8; RFC 5280 §4.2.2.2)"
    )]
    MissingSia,

    #[error("CA certificate SIA must contain id-ad-caRepository rsync URI (RFC 6487 §4.8.8.1)")]
    MissingCaRepository,

    #[error("CA certificate SIA must contain id-ad-rpkiManifest rsync URI (RFC 6487 §4.8.8.2)")]
    MissingRpkiManifest,

    #[error(
        "SIA id-ad-caRepository accessLocation must be rsync:// URI, got {0} (RFC 6487 §4.8.8.1)"
    )]
    CaRepositoryNotRsync(String),

    #[error(
        "SIA id-ad-rpkiManifest accessLocation must be rsync:// URI, got {0} (RFC 6487 §4.8.8.2)"
    )]
    RpkiManifestNotRsync(String),

    #[error(
        "SIA id-ad-rpkiNotify accessLocation must be https:// URI, got {0} (RFC 8182 §3.4.1; RFC 6487 §4.8.8.3)"
    )]
    RpkiNotifyNotHttps(String),

    #[error(
        "manifest rsync URI must be under CA publication point: manifest={manifest_rsync_uri} publication_point={publication_point_rsync_uri} (RFC 9286 §6.1)"
    )]
    ManifestNotUnderPublicationPoint {
        manifest_rsync_uri: String,
        publication_point_rsync_uri: String,
    },
}

pub fn ca_instance_uris_from_ca_certificate(
    cert: &ResourceCertificate,
) -> Result<CaInstanceUris, CaInstanceUrisError> {
    if cert.kind != ResourceCertKind::Ca {
        return Err(CaInstanceUrisError::NotCa);
    }

    let sia = cert
        .tbs
        .extensions
        .subject_info_access
        .as_ref()
        .ok_or(CaInstanceUrisError::MissingSia)?;

    let access_descriptions = match sia {
        SubjectInfoAccess::Ca(ca) => &ca.access_descriptions,
        SubjectInfoAccess::Ee(_ee) => return Err(CaInstanceUrisError::MissingSia),
    };

    let mut ca_repo: Option<String> = None;
    let mut manifest: Option<String> = None;
    let mut notify: Option<String> = None;

    for ad in access_descriptions {
        if ad.access_method_oid == OID_AD_CA_REPOSITORY {
            let u = ad.access_location.as_str();
            if !u.starts_with("rsync://") {
                return Err(CaInstanceUrisError::CaRepositoryNotRsync(u.to_string()));
            }
            ca_repo.get_or_insert(u.to_string());
        } else if ad.access_method_oid == OID_AD_RPKI_MANIFEST {
            let u = ad.access_location.as_str();
            if !u.starts_with("rsync://") {
                return Err(CaInstanceUrisError::RpkiManifestNotRsync(u.to_string()));
            }
            manifest.get_or_insert(u.to_string());
        } else if ad.access_method_oid == OID_AD_RPKI_NOTIFY {
            let u = ad.access_location.as_str();
            if !u.starts_with("https://") {
                return Err(CaInstanceUrisError::RpkiNotifyNotHttps(u.to_string()));
            }
            notify.get_or_insert(u.to_string());
        }
    }

    let mut publication_point_rsync_uri =
        ca_repo.ok_or(CaInstanceUrisError::MissingCaRepository)?;
    if !publication_point_rsync_uri.ends_with('/') {
        publication_point_rsync_uri.push('/');
    }

    let manifest_rsync_uri = manifest.ok_or(CaInstanceUrisError::MissingRpkiManifest)?;
    if !manifest_rsync_uri.starts_with(&publication_point_rsync_uri) {
        return Err(CaInstanceUrisError::ManifestNotUnderPublicationPoint {
            manifest_rsync_uri,
            publication_point_rsync_uri,
        });
    }
    let manifest_parent = manifest_rsync_uri
        .rsplit_once('/')
        .map(|(parent, _)| format!("{parent}/"))
        .ok_or_else(|| CaInstanceUrisError::ManifestNotUnderPublicationPoint {
            manifest_rsync_uri: manifest_rsync_uri.clone(),
            publication_point_rsync_uri: publication_point_rsync_uri.clone(),
        })?;

    Ok(CaInstanceUris {
        rsync_base_uri: manifest_parent,
        manifest_rsync_uri,
        publication_point_rsync_uri,
        rrdp_notification_uri: notify,
    })
}
