use std::io;

use schannel::{RawPointer, cert_context::ValidUses, cert_store::CertStore};
use windows_sys::Win32::{
    Foundation::{CRYPT_E_NOT_FOUND, GetLastError},
    Security::Cryptography::{
        CERT_CONTEXT, CRL_CONTEXT, CertCloseStore, CertEnumCRLsInStore, CertOpenSystemStoreW,
        PKCS_7_ASN_ENCODING, X509_ASN_ENCODING,
    },
};

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EncodingType {
    X509Asn,
    Pkcs7Asn,
    Other(u32),
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CertificateUses {
    All,
    Oids(Vec<String>),
}

#[derive(Debug)]
pub struct CertificateEntry {
    pub der: Vec<u8>,
    pub encoding: EncodingType,
    pub valid_uses: io::Result<CertificateUses>,
}

#[derive(Debug)]
pub struct CertificateEntries {
    pub had_open_store: bool,
    pub entries: Vec<CertificateEntry>,
}

#[derive(Debug)]
pub struct CrlEntry {
    pub der: Vec<u8>,
    pub encoding: EncodingType,
}

fn encoding_type(raw: u32) -> EncodingType {
    if raw & X509_ASN_ENCODING != 0 {
        EncodingType::X509Asn
    } else if raw & PKCS_7_ASN_ENCODING != 0 {
        EncodingType::Pkcs7Asn
    } else {
        EncodingType::Other(raw)
    }
}

pub fn enum_certificates(store_name: &str) -> CertificateEntries {
    let open_fns = [CertStore::open_current_user, CertStore::open_local_machine];
    let mut had_open_store = false;
    let mut entries = Vec::new();

    for open in open_fns {
        let Ok(store) = open(store_name) else {
            continue;
        };
        had_open_store = true;

        for cert in store.certs() {
            let encoding = unsafe {
                let ptr = cert.as_ptr() as *const CERT_CONTEXT;
                encoding_type((*ptr).dwCertEncodingType)
            };
            let valid_uses = cert.valid_uses().map_or_else(
                |err| Err(io::Error::other(err)),
                |uses| {
                    Ok(match uses {
                        ValidUses::All => CertificateUses::All,
                        ValidUses::Oids(oids) => CertificateUses::Oids(oids.into_iter().collect()),
                    })
                },
            );
            entries.push(CertificateEntry {
                der: cert.to_der().to_owned(),
                encoding,
                valid_uses,
            });
        }
    }

    CertificateEntries {
        had_open_store,
        entries,
    }
}

pub fn enum_crls(store_name: &str) -> io::Result<Vec<CrlEntry>> {
    let store_name_wide: Vec<u16> = store_name
        .encode_utf16()
        .chain(core::iter::once(0))
        .collect();

    let store = unsafe { CertOpenSystemStoreW(0, store_name_wide.as_ptr()) };
    if store.is_null() {
        return Err(io::Error::last_os_error());
    }

    let mut result = Vec::new();
    let mut crl_context: *const CRL_CONTEXT = core::ptr::null();
    loop {
        crl_context = unsafe { CertEnumCRLsInStore(store, crl_context) };
        if crl_context.is_null() {
            let err = unsafe { GetLastError() };
            if err != CRYPT_E_NOT_FOUND as u32 {
                unsafe {
                    CertCloseStore(store, 0);
                }
                return Err(io::Error::from_raw_os_error(err as i32));
            }
            break;
        }

        let crl = unsafe { &*crl_context };
        let der =
            unsafe { core::slice::from_raw_parts(crl.pbCrlEncoded, crl.cbCrlEncoded as usize) }
                .to_vec();
        result.push(CrlEntry {
            der,
            encoding: encoding_type(crl.dwCertEncodingType),
        });
    }

    unsafe {
        CertCloseStore(store, 0);
    }

    Ok(result)
}
