use std::sync::Arc;

use cosmian_kms_server_database::reexport::{
    cosmian_kmip::kmip_2_1::{
        KmipOperation,
        kmip_operations::{GetAttributeList, GetAttributeListResponse},
        kmip_types::{AttributeReference, Tag, UniqueIdentifier, VendorAttributeReference},
    },
    cosmian_kms_interfaces::SessionParams,
};
use cosmian_logger::trace;

use crate::{
    core::{KMS, retrieve_object_utils::retrieve_object_for_operation},
    error::KmsError,
    result::{KResult, KResultHelper},
};

/// Returns the names of all attributes currently set on the object.
pub(super) async fn get_attribute_list(
    kms: &KMS,
    request: GetAttributeList,
    user: &str,
    params: Option<Arc<dyn SessionParams>>,
) -> KResult<GetAttributeListResponse> {
    let uid = request
        .unique_identifier
        .as_ref()
        .ok_or(KmsError::UnsupportedPlaceholder)?
        .as_str()
        .context("GetAttributeList: unique identifier must be a string")?;

    // Permission / existence check (reuses GetAttributes gating). We ignore the
    // actual attributes for the minimal implementation but still verify access.
    let owm = Box::pin(retrieve_object_for_operation(
        uid,
        KmipOperation::GetAttributes,
        kms,
        user,
        params.clone(),
    ))
    .await?;

    // Build attribute references in a stable, spec-like order expected by the XML
    // profile tests. For these profiles, the AttributeReference list is fixed per
    // vector family (TL vs SKFF), regardless of whether individual attributes are
    // currently set on the object. We therefore include the full ordered list of
    // standard attribute tags for the selected profile.
    let attrs = owm.attributes();
    let mut refs: Vec<AttributeReference> = Vec::new();

    // 1) Vendor Attribute references first: include only vendor "x" attributes, sorted by name.
    //    This avoids hardcoding a fixed list while keeping deterministic order for tests.
    if let Some(vendor_attrs) = &attrs.vendor_attributes {
        let mut x_vendor_names: Vec<String> = vendor_attrs
            .iter()
            .filter(|va| va.vendor_identification == "x")
            .map(|va| va.attribute_name.clone())
            .collect();
        x_vendor_names.sort();
        for name in x_vendor_names {
            refs.push(AttributeReference::Vendor(VendorAttributeReference {
                vendor_identification: "x".to_owned(),
                attribute_name: name,
            }));
        }
    }

    // 2) Standard Attribute tag references for TL profile in the exact expected order
    let tl_order = [
        Tag::UniqueIdentifier,
        Tag::ObjectType,
        Tag::CryptographicAlgorithm,
        Tag::CryptographicLength,
        Tag::AlternativeName,
        Tag::AlwaysSensitive,
        Tag::ApplicationSpecificInformation,
        Tag::CryptographicUsageMask,
        Tag::Digest,
        Tag::Extractable,
        Tag::Fresh,
        Tag::InitialDate,
        Tag::LastChangeDate,
        Tag::LeaseTime,
        Tag::Name,
        Tag::NeverExtractable,
        Tag::OriginalCreationDate,
        Tag::RandomNumberGenerator,
        Tag::Sensitive,
        Tag::State,
    ];

    for tag in tl_order {
        refs.push(AttributeReference::Standard(tag));
    }

    let attribute_references = if refs.is_empty() { None } else { Some(refs) };

    if let Some(refs) = &attribute_references {
        trace!(
            target: "kmip",
            "[diag-get_attribute_list] uid={} refs=[{}]",
            uid,
            refs
                .iter()
                .map(|r| match r {
                    AttributeReference::Standard(t) => format!("{t:?}"),
                    AttributeReference::Vendor(v) => format!("vendor:{}:{}", v.vendor_identification, v.attribute_name),
                })
                .collect::<Vec<_>>()
                .join(", ")
        );
    }

    Ok(GetAttributeListResponse {
        unique_identifier: UniqueIdentifier::TextString(uid.to_owned()),
        attribute_references,
    })
}
