use std::collections::HashMap;

use crate::client::Client;
use crate::error::Error;
use crate::types::LicensePublicKey;

use super::license::{activate_or_refresh, ActivateOrRefreshOptions, VerifiedLicense};

/// Holds license signing public keys indexed by `key_id`. Seeds from a baked
/// key string, merges keys fetched from the API, and exposes a snapshot for
/// [`activate_or_refresh_with_keys`].
#[derive(Debug, Default, Clone)]
pub struct PublicKeyStore {
    keys: HashMap<String, String>,
}

impl PublicKeyStore {
    pub fn new() -> Self {
        Self::default()
    }

    /// Seed from a baked `"key_id:base64,key_id:base64"` string. An entry
    /// without a colon is stored under the `"default"` key id.
    pub fn parse(baked: &str) -> Self {
        let mut store = Self::new();
        for raw in baked.split(',') {
            let entry = raw.trim();
            if entry.is_empty() {
                continue;
            }
            match entry.split_once(':') {
                Some((key_id, encoded)) => {
                    let encoded = encoded.trim();
                    if !encoded.is_empty() {
                        store
                            .keys
                            .insert(key_id.trim().to_string(), encoded.to_string());
                    }
                }
                None => {
                    store.keys.insert("default".to_string(), entry.to_string());
                }
            }
        }
        store
    }

    pub fn snapshot(&self) -> HashMap<String, String> {
        self.keys.clone()
    }

    pub fn merge_keys(&mut self, keys: &[LicensePublicKey]) {
        for k in keys {
            if !k.key_id.is_empty() && !k.public_key_base64.is_empty() {
                self.keys
                    .insert(k.key_id.clone(), k.public_key_base64.clone());
            }
        }
    }

    pub fn len(&self) -> usize {
        self.keys.len()
    }

    pub fn is_empty(&self) -> bool {
        self.keys.is_empty()
    }
}

fn is_unknown_signing_key(error: &Error) -> bool {
    matches!(error, Error::Api { code, .. } if code.contains("unknown signing key_id"))
}

/// Run [`activate_or_refresh`] against the store. On an unknown signing
/// `key_id` it fetches the latest public keys, merges them, and retries once,
/// removing the per-application key cache and rotation handling.
pub async fn activate_or_refresh_with_keys(
    client: &Client,
    opts: &ActivateOrRefreshOptions<'_>,
    store: &mut PublicKeyStore,
) -> Result<VerifiedLicense, Error> {
    match activate_or_refresh(client, opts, &store.snapshot()).await {
        Ok(verified) => Ok(verified),
        Err(error) if is_unknown_signing_key(&error) => {
            let response = client.public_license_keys().await.map_err(|_| error)?;
            store.merge_keys(&response.keys);
            activate_or_refresh(client, opts, &store.snapshot()).await
        }
        Err(error) => Err(error),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_keyed_and_bare() {
        let store = PublicKeyStore::parse("k1:AAA, BBB ,k2:CCC");
        let snap = store.snapshot();
        assert_eq!(snap.get("k1").map(String::as_str), Some("AAA"));
        assert_eq!(snap.get("k2").map(String::as_str), Some("CCC"));
        assert_eq!(snap.get("default").map(String::as_str), Some("BBB"));
    }

    #[test]
    fn merge_skips_empty_ids() {
        let mut store = PublicKeyStore::new();
        store.merge_keys(&[
            LicensePublicKey {
                key_id: "k1".to_string(),
                algorithm: "ed25519".to_string(),
                public_key_base64: "AAA".to_string(),
            },
            LicensePublicKey {
                key_id: String::new(),
                algorithm: "ed25519".to_string(),
                public_key_base64: "skip".to_string(),
            },
        ]);
        assert_eq!(store.len(), 1);
        assert_eq!(store.snapshot().get("k1").map(String::as_str), Some("AAA"));
    }
}
