//! API Key Service
//!
//! Core functionality for API key management

use crate::core::api_key::models::*;
use anyhow::Result;
use chrono::{DateTime, Duration, Utc};
use sha2::{Digest, Sha256};
use subtle::ConstantTimeEq;
use uuid::Uuid;

pub struct ApiKeyService {
    _db_url: String,
    config: AnomalyDetectionConfig,
}

impl ApiKeyService {
    pub fn new(db_url: String) -> Self {
        Self {
            _db_url: db_url,
            config: AnomalyDetectionConfig::default(),
        }
    }

    pub fn with_config(db_url: String, config: AnomalyDetectionConfig) -> Self {
        Self {
            _db_url: db_url,
            config,
        }
    }

    pub fn generate_key(&self, key_type: ApiKeyType) -> (String, String, String) {
        let uuid = Uuid::new_v4().to_string().replace("-", "");
        let timestamp = Utc::now().timestamp();
        let random_part = Uuid::new_v4().to_string().replace("-", "")[..8].to_string();

        let key = format!(
            "{}{}_{}_{}",
            key_type.prefix(),
            uuid,
            timestamp,
            random_part
        );
        let hash = self.hash_key(&key);

        (key, hash, uuid)
    }

    pub fn hash_key(&self, key: &str) -> String {
        let mut hasher = Sha256::new();
        hasher.update(key.as_bytes());
        format!("{:x}", hasher.finalize())
    }

    /// Constant-time comparison of two hash strings
    ///
    /// This prevents timing attacks when comparing sensitive data like
    /// API key hashes. Use this instead of `==` for security-critical comparisons.
    pub fn constant_time_compare(a: &str, b: &str) -> bool {
        if a.len() != b.len() {
            return false;
        }
        a.as_bytes().ct_eq(b.as_bytes()).into()
    }

    pub fn create_key(&self, request: CreateApiKeyRequest) -> Result<CreateApiKeyResponse> {
        let ttl_days = request
            .ttl_days
            .unwrap_or(request.key_type.default_ttl_days());
        let expires_at = Utc::now() + Duration::days(ttl_days);

        let (key, _key_hash, _) = self.generate_key(request.key_type);

        let warning = if request.key_type == ApiKeyType::Emergency {
            "警告：緊急 Key 將在 24 小時後自動過期，請及時更新".to_string()
        } else if ttl_days < 30 {
            format!("警告：Key 有效期僅 {} 天，建議使用更長的有效期", ttl_days)
        } else {
            String::new()
        };

        Ok(CreateApiKeyResponse {
            key_id: self.extract_key_id(&key),
            key,
            expires_at,
            warning,
        })
    }

    pub fn extract_key_id(&self, key: &str) -> String {
        let parts: Vec<&str> = key.split('_').collect();
        if parts.len() >= 2 {
            format!("{}_{}", parts[0], parts[1])
        } else {
            key[..16.min(key.len())].to_string()
        }
    }

    pub fn validate_key(&self, request: ValidateApiKeyRequest) -> Result<ValidateApiKeyResponse> {
        let _key_hash = self.hash_key(&request.key);

        Ok(ValidateApiKeyResponse {
            valid: true,
            key_id: Some(self.extract_key_id(&request.key)),
            permissions: Some(vec!["read".to_string(), "write".to_string()]),
            error: None,
            requires_rotation: false,
        })
    }

    pub fn require_rotation(&self, key_id: &str, reason: &str) -> Result<()> {
        tracing::info!("API Key {} requires rotation: {}", key_id, reason);
        Ok(())
    }

    pub fn check_anomaly(&self, key_id: &str, metrics: &AnomalyMetrics) -> Option<AnomalyRecord> {
        if metrics.requests_per_minute > self.config.requests_per_minute_threshold {
            return Some(AnomalyRecord {
                id: 0,
                key_id: key_id.to_string(),
                anomaly_type: AnomalyType::HighRequestRate,
                severity: AnomalySeverity::Medium,
                ip_address: metrics.last_ip.clone(),
                request_count: Some(metrics.requests_per_minute),
                error_count: Some(metrics.error_count),
                error_rate: Some(metrics.error_rate),
                unique_ips: Some(metrics.unique_ips),
                details: None,
                resolved: false,
                resolved_at: None,
                resolved_by: None,
                created_at: Utc::now(),
            });
        }

        if metrics.error_rate > self.config.error_rate_threshold {
            return Some(AnomalyRecord {
                id: 0,
                key_id: key_id.to_string(),
                anomaly_type: AnomalyType::HighErrorRate,
                severity: AnomalySeverity::Medium,
                ip_address: metrics.last_ip.clone(),
                request_count: Some(metrics.requests_per_minute),
                error_count: Some(metrics.error_count),
                error_rate: Some(metrics.error_rate),
                unique_ips: Some(metrics.unique_ips),
                details: None,
                resolved: false,
                resolved_at: None,
                resolved_by: None,
                created_at: Utc::now(),
            });
        }

        if metrics.unique_ips > self.config.unique_ips_per_hour_threshold {
            return Some(AnomalyRecord {
                id: 0,
                key_id: key_id.to_string(),
                anomaly_type: AnomalyType::MultipleIps,
                severity: AnomalySeverity::Low,
                ip_address: None,
                request_count: None,
                error_count: None,
                error_rate: None,
                unique_ips: Some(metrics.unique_ips),
                details: None,
                resolved: false,
                resolved_at: None,
                resolved_by: None,
                created_at: Utc::now(),
            });
        }

        None
    }

    pub fn calculate_grace_period_end(&self, key_type: ApiKeyType) -> DateTime<Utc> {
        Utc::now() + Duration::hours(key_type.grace_period_hours())
    }

    pub fn is_in_grace_period(&self, grace_period_end: Option<DateTime<Utc>>) -> bool {
        match grace_period_end {
            Some(end) => Utc::now() < end,
            None => false,
        }
    }

    pub fn get_key_prefix(&self, key_type: ApiKeyType) -> &'static str {
        key_type.prefix()
    }

    pub fn get_config(&self) -> &AnomalyDetectionConfig {
        &self.config
    }
}

#[derive(Debug, Clone)]
pub struct AnomalyMetrics {
    pub requests_per_minute: i32,
    pub error_count: i32,
    pub error_rate: f64,
    pub unique_ips: i32,
    pub last_ip: Option<String>,
}

impl Default for AnomalyMetrics {
    fn default() -> Self {
        Self {
            requests_per_minute: 0,
            error_count: 0,
            error_rate: 0.0,
            unique_ips: 0,
            last_ip: None,
        }
    }
}

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

    #[test]
    fn test_generate_key() {
        let service = ApiKeyService::new("postgres://localhost".to_string());

        let (key, hash, _) = service.generate_key(ApiKeyType::User);

        assert!(key.starts_with("muser_"));
        assert_eq!(hash.len(), 64);
    }

    #[test]
    fn test_hash_key() {
        let service = ApiKeyService::new("postgres://localhost".to_string());

        let hash = service.hash_key("test_key");

        assert_eq!(hash.len(), 64);
    }

    #[test]
    fn test_extract_key_id() {
        let service = ApiKeyService::new("postgres://localhost".to_string());

        let key_id = service.extract_key_id("muser_a1b2c3d4_1710998400_abc12345");

        assert_eq!(key_id, "muser_a1b2c3d4");
    }

    #[test]
    fn test_grace_period() {
        let service = ApiKeyService::new("postgres://localhost".to_string());

        let end = service.calculate_grace_period_end(ApiKeyType::User);

        let hours = (end - Utc::now()).num_hours();
        assert!(
            hours >= 23 && hours <= 24,
            "expected 23-24 hours, got {}",
            hours
        );
    }

    #[test]
    fn test_constant_time_compare() {
        let a = "abcdef1234567890";
        let b = "abcdef1234567890";
        let c = "abcdef1234567891";
        let d = "short";

        assert!(ApiKeyService::constant_time_compare(a, b));
        assert!(!ApiKeyService::constant_time_compare(a, c));
        assert!(!ApiKeyService::constant_time_compare(a, d));
        assert!(!ApiKeyService::constant_time_compare(d, a));
    }
}
