//! API versioning support.
//!
//! Implements multiple versioning strategies:
//! - URL path versioning (/v1/..., /v2/...)
//! - Header-based versioning (Accept-Version, X-API-Version)
//! - Query parameter versioning (?version=1)
//!
//! ## Deprecation Policy
//!
//! - Major versions are supported for 12 months after deprecation
//! - Deprecated versions return `Deprecation` and `Sunset` headers
//! - Breaking changes only in major version bumps

use axum::{
    extract::Request,
    http::{header::HeaderName, HeaderValue, StatusCode},
    middleware::Next,
    response::{IntoResponse, Response},
};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

// ============================================================================
// Version Types
// ============================================================================

/// API version identifier.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct ApiVersion {
    /// Major version (breaking changes)
    pub major: u32,
    /// Minor version (new features, backwards compatible)
    pub minor: u32,
}

impl ApiVersion {
    /// Create a new API version.
    pub const fn new(major: u32, minor: u32) -> Self {
        Self { major, minor }
    }

    /// Parse from string like "1", "1.0", "v1", "v1.2".
    pub fn parse(s: &str) -> Option<Self> {
        let s = s.trim().trim_start_matches('v').trim_start_matches('V');

        if let Some((major, minor)) = s.split_once('.') {
            let major = major.parse().ok()?;
            let minor = minor.parse().ok()?;
            Some(Self { major, minor })
        } else {
            let major = s.parse().ok()?;
            Some(Self { major, minor: 0 })
        }
    }

    /// Check if this version is compatible with another.
    pub fn is_compatible_with(&self, other: &Self) -> bool {
        self.major == other.major && self.minor >= other.minor
    }
}

impl std::fmt::Display for ApiVersion {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}.{}", self.major, self.minor)
    }
}

impl Default for ApiVersion {
    fn default() -> Self {
        Self::new(1, 0)
    }
}

// ============================================================================
// Version Constants
// ============================================================================

/// Current API version.
pub const CURRENT_VERSION: ApiVersion = ApiVersion::new(1, 0);

/// Minimum supported version.
pub const MIN_SUPPORTED_VERSION: ApiVersion = ApiVersion::new(1, 0);

/// Version currently in development (preview).
pub const PREVIEW_VERSION: ApiVersion = ApiVersion::new(2, 0);

// ============================================================================
// Version Info
// ============================================================================

/// Information about a specific API version.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionInfo {
    /// The version identifier
    pub version: ApiVersion,
    /// Whether this version is the current default
    pub is_current: bool,
    /// Whether this version is deprecated
    pub is_deprecated: bool,
    /// Deprecation date (if deprecated)
    pub deprecated_at: Option<DateTime<Utc>>,
    /// Sunset date (when version will be removed)
    pub sunset_at: Option<DateTime<Utc>>,
    /// Release notes URL
    pub release_notes_url: Option<String>,
    /// Changes from previous version
    pub changes: Vec<String>,
}

/// API version registry.
#[derive(Debug, Clone)]
pub struct VersionRegistry {
    versions: HashMap<(u32, u32), VersionInfo>,
    current: ApiVersion,
}

impl Default for VersionRegistry {
    fn default() -> Self {
        Self::new()
    }
}

impl VersionRegistry {
    /// Create a new version registry with standard versions.
    pub fn new() -> Self {
        let mut registry = Self {
            versions: HashMap::new(),
            current: CURRENT_VERSION,
        };

        // Register v1.0 (current)
        registry.register(VersionInfo {
            version: ApiVersion::new(1, 0),
            is_current: true,
            is_deprecated: false,
            deprecated_at: None,
            sunset_at: None,
            release_notes_url: Some("https://docs.synor.io/api/v1/release-notes".to_string()),
            changes: vec![
                "Initial API release".to_string(),
                "Wallet, RPC, Storage, DEX, IBC, ZK services".to_string(),
                "WebSocket event streaming".to_string(),
            ],
        });

        // Register v2.0 (preview)
        registry.register(VersionInfo {
            version: ApiVersion::new(2, 0),
            is_current: false,
            is_deprecated: false,
            deprecated_at: None,
            sunset_at: None,
            release_notes_url: None,
            changes: vec![
                "Preview version - not for production use".to_string(),
                "GraphQL API support".to_string(),
                "Enhanced batch operations".to_string(),
            ],
        });

        registry
    }

    /// Register a version.
    pub fn register(&mut self, info: VersionInfo) {
        self.versions.insert((info.version.major, info.version.minor), info);
    }

    /// Get version info.
    pub fn get(&self, version: &ApiVersion) -> Option<&VersionInfo> {
        self.versions.get(&(version.major, version.minor))
    }

    /// Get the current version.
    pub fn current(&self) -> &ApiVersion {
        &self.current
    }

    /// Check if a version is supported.
    pub fn is_supported(&self, version: &ApiVersion) -> bool {
        self.versions.contains_key(&(version.major, version.minor))
            && *version >= MIN_SUPPORTED_VERSION
    }

    /// Get all supported versions.
    pub fn supported_versions(&self) -> Vec<&VersionInfo> {
        self.versions
            .values()
            .filter(|v| v.version >= MIN_SUPPORTED_VERSION)
            .collect()
    }
}

// ============================================================================
// Version Extraction
// ============================================================================

/// How the API version was specified.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VersionSource {
    /// From URL path (/v1/...)
    UrlPath,
    /// From Accept-Version header
    AcceptHeader,
    /// From X-API-Version header
    CustomHeader,
    /// From query parameter (?version=1)
    QueryParam,
    /// Default version (not explicitly specified)
    Default,
}

/// Extracted API version with metadata.
#[derive(Debug, Clone)]
pub struct ExtractedVersion {
    /// The API version
    pub version: ApiVersion,
    /// How it was determined
    pub source: VersionSource,
}

/// Extract API version from request.
pub fn extract_version(req: &Request) -> ExtractedVersion {
    // 1. Check Accept-Version header
    if let Some(value) = req.headers().get("accept-version") {
        if let Ok(s) = value.to_str() {
            if let Some(version) = ApiVersion::parse(s) {
                return ExtractedVersion {
                    version,
                    source: VersionSource::AcceptHeader,
                };
            }
        }
    }

    // 2. Check X-API-Version header
    if let Some(value) = req.headers().get("x-api-version") {
        if let Ok(s) = value.to_str() {
            if let Some(version) = ApiVersion::parse(s) {
                return ExtractedVersion {
                    version,
                    source: VersionSource::CustomHeader,
                };
            }
        }
    }

    // 3. Check query parameter
    if let Some(query) = req.uri().query() {
        for pair in query.split('&') {
            if let Some((key, value)) = pair.split_once('=') {
                if key == "version" || key == "api_version" {
                    if let Some(version) = ApiVersion::parse(value) {
                        return ExtractedVersion {
                            version,
                            source: VersionSource::QueryParam,
                        };
                    }
                }
            }
        }
    }

    // 4. Extract from URL path
    let path = req.uri().path();
    if path.starts_with("/v") {
        if let Some(version_str) = path.split('/').nth(1) {
            if let Some(version) = ApiVersion::parse(version_str) {
                return ExtractedVersion {
                    version,
                    source: VersionSource::UrlPath,
                };
            }
        }
    }

    // 5. Default version
    ExtractedVersion {
        version: CURRENT_VERSION,
        source: VersionSource::Default,
    }
}

// ============================================================================
// Versioning Middleware
// ============================================================================

/// Custom header names.
static X_API_VERSION: HeaderName = HeaderName::from_static("x-api-version");
static X_API_DEPRECATED: HeaderName = HeaderName::from_static("x-api-deprecated");
static DEPRECATION: HeaderName = HeaderName::from_static("deprecation");
static SUNSET: HeaderName = HeaderName::from_static("sunset");

/// Version validation middleware.
pub async fn version_middleware(req: Request, next: Next) -> Response {
    let registry = VersionRegistry::new();
    let extracted = extract_version(&req);

    // Check if version is supported
    if !registry.is_supported(&extracted.version) {
        return (
            StatusCode::BAD_REQUEST,
            [(X_API_VERSION.clone(), HeaderValue::from_static("1.0"))],
            format!(
                "API version {} is not supported. Supported versions: {}",
                extracted.version,
                registry
                    .supported_versions()
                    .iter()
                    .map(|v| v.version.to_string())
                    .collect::<Vec<_>>()
                    .join(", ")
            ),
        )
            .into_response();
    }

    // Continue with request
    let mut response = next.run(req).await;

    // Add version headers to response
    let headers = response.headers_mut();

    // Always include current version
    if let Ok(v) = HeaderValue::from_str(&extracted.version.to_string()) {
        headers.insert(X_API_VERSION.clone(), v);
    }

    // Add deprecation headers if needed
    if let Some(info) = registry.get(&extracted.version) {
        if info.is_deprecated {
            headers.insert(
                X_API_DEPRECATED.clone(),
                HeaderValue::from_static("true"),
            );

            if let Some(deprecated_at) = &info.deprecated_at {
                if let Ok(v) = HeaderValue::from_str(&deprecated_at.to_rfc3339()) {
                    headers.insert(DEPRECATION.clone(), v);
                }
            }

            if let Some(sunset_at) = &info.sunset_at {
                if let Ok(v) = HeaderValue::from_str(&sunset_at.to_rfc3339()) {
                    headers.insert(SUNSET.clone(), v);
                }
            }
        }
    }

    response
}

// ============================================================================
// Version Response Types
// ============================================================================

/// Response for version information endpoint.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionsResponse {
    /// Current version
    pub current: String,
    /// All supported versions
    pub supported: Vec<VersionDetail>,
    /// Deprecated versions
    pub deprecated: Vec<VersionDetail>,
}

/// Detailed version information.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionDetail {
    /// Version string
    pub version: String,
    /// Status (current, supported, deprecated, preview)
    pub status: String,
    /// When deprecated (if applicable)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub deprecated_at: Option<String>,
    /// When it will be removed (if applicable)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sunset_at: Option<String>,
    /// URL to release notes
    #[serde(skip_serializing_if = "Option::is_none")]
    pub release_notes: Option<String>,
}

impl VersionsResponse {
    /// Build version response from registry.
    pub fn from_registry(registry: &VersionRegistry) -> Self {
        let mut supported = Vec::new();
        let mut deprecated = Vec::new();

        for info in registry.supported_versions() {
            let detail = VersionDetail {
                version: info.version.to_string(),
                status: if info.is_current {
                    "current".to_string()
                } else if info.is_deprecated {
                    "deprecated".to_string()
                } else if info.version == PREVIEW_VERSION {
                    "preview".to_string()
                } else {
                    "supported".to_string()
                },
                deprecated_at: info.deprecated_at.map(|d| d.to_rfc3339()),
                sunset_at: info.sunset_at.map(|d| d.to_rfc3339()),
                release_notes: info.release_notes_url.clone(),
            };

            if info.is_deprecated {
                deprecated.push(detail);
            } else {
                supported.push(detail);
            }
        }

        Self {
            current: registry.current().to_string(),
            supported,
            deprecated,
        }
    }
}

// ============================================================================
// Routes
// ============================================================================

use axum::{routing::get, Json, Router};
use crate::routes::AppState;

/// Build version routes.
pub fn router() -> Router<AppState> {
    Router::new()
        .route("/versions", get(get_versions))
        .route("/version", get(get_current_version))
}

/// Get all API versions.
async fn get_versions() -> Json<VersionsResponse> {
    let registry = VersionRegistry::new();
    Json(VersionsResponse::from_registry(&registry))
}

/// Get current API version.
async fn get_current_version() -> Json<serde_json::Value> {
    Json(serde_json::json!({
        "version": CURRENT_VERSION.to_string(),
        "major": CURRENT_VERSION.major,
        "minor": CURRENT_VERSION.minor,
    }))
}

// ============================================================================
// Tests
// ============================================================================

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

    #[test]
    fn test_version_parsing() {
        assert_eq!(ApiVersion::parse("1"), Some(ApiVersion::new(1, 0)));
        assert_eq!(ApiVersion::parse("1.0"), Some(ApiVersion::new(1, 0)));
        assert_eq!(ApiVersion::parse("v1"), Some(ApiVersion::new(1, 0)));
        assert_eq!(ApiVersion::parse("v2.1"), Some(ApiVersion::new(2, 1)));
        assert_eq!(ApiVersion::parse("V1.5"), Some(ApiVersion::new(1, 5)));
        assert_eq!(ApiVersion::parse("invalid"), None);
    }

    #[test]
    fn test_version_compatibility() {
        let v1_0 = ApiVersion::new(1, 0);
        let v1_1 = ApiVersion::new(1, 1);
        let v2_0 = ApiVersion::new(2, 0);

        assert!(v1_1.is_compatible_with(&v1_0));
        assert!(!v1_0.is_compatible_with(&v1_1));
        assert!(!v2_0.is_compatible_with(&v1_0));
    }

    #[test]
    fn test_version_registry() {
        let registry = VersionRegistry::new();

        assert!(registry.is_supported(&ApiVersion::new(1, 0)));
        assert!(registry.is_supported(&ApiVersion::new(2, 0)));
        assert!(!registry.is_supported(&ApiVersion::new(3, 0)));
    }

    #[test]
    fn test_version_display() {
        assert_eq!(ApiVersion::new(1, 0).to_string(), "1.0");
        assert_eq!(ApiVersion::new(2, 5).to_string(), "2.5");
    }
}
