use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};

use parking_lot::Mutex;
use reqwest::Client;
use serde::Deserialize;
use tracing::{info, warn};

use crate::error::{RegistryError, Result};

const TOKEN_TTL: Duration = Duration::from_secs(300); // 5-minute in-memory cache

#[derive(Debug, Deserialize)]
struct SecretResponse {
    value: String,
}

struct CachedToken {
    value: String,
    expires_at: Instant,
}

/// Resolves `vault://path` references to their plaintext values
/// via the vault-service HTTP API.
///
/// Tokens are cached in-memory for `TOKEN_TTL` to avoid repeated requests
/// on every client creation.
pub struct VaultResolver {
    vault_url: String,
    http: Client,
    cache: Arc<Mutex<HashMap<String, CachedToken>>>,
}

impl VaultResolver {
    pub fn new(vault_url: String) -> Self {
        Self {
            vault_url,
            http: Client::builder()
                .timeout(Duration::from_secs(5))
                .build()
                .expect("reqwest client"),
            cache: Arc::new(Mutex::new(HashMap::new())),
        }
    }

    /// Resolve `vault://path/to/secret` → plaintext value.
    ///
    /// Returns `None` if the path is not a `vault://` reference.
    pub async fn try_resolve(&self, token_path: &str) -> Option<Result<String>> {
        let secret_path = token_path.strip_prefix("vault://")?;

        // Check cache first
        {
            let cache = self.cache.lock();
            if let Some(cached) = cache.get(secret_path) {
                if cached.expires_at > Instant::now() {
                    return Some(Ok(cached.value.clone()));
                }
            }
        }

        // Fetch from vault-service
        let url = format!("{}/v1/{}", self.vault_url, secret_path);
        let resp = match self.http.get(&url).send().await {
            Ok(r) => r,
            Err(e) => {
                return Some(Err(RegistryError::Config(format!(
                    "vault resolve HTTP error for '{}': {}",
                    secret_path, e
                ))))
            }
        };

        if !resp.status().is_success() {
            return Some(Err(RegistryError::Config(format!(
                "vault resolve failed for '{}': HTTP {}",
                secret_path,
                resp.status()
            ))));
        }

        let body: SecretResponse = match resp.json().await {
            Ok(b) => b,
            Err(e) => {
                return Some(Err(RegistryError::Config(format!(
                    "vault resolve parse error for '{}': {}",
                    secret_path, e
                ))))
            }
        };

        info!(path = %secret_path, "Token resolved from vault");

        // Cache the result
        {
            let mut cache = self.cache.lock();
            cache.insert(
                secret_path.to_string(),
                CachedToken {
                    value: body.value.clone(),
                    expires_at: Instant::now() + TOKEN_TTL,
                },
            );
        }

        Some(Ok(body.value))
    }

    /// Expire all cached tokens, forcing re-resolution on next access.
    pub fn invalidate_cache(&self) {
        self.cache.lock().clear();
        warn!("VaultResolver: token cache invalidated");
    }
}
