use std::sync::Arc;
use std::time::Instant;

use axum::extract::{Path, Query, State};
use axum::http::{header, StatusCode};
use axum::response::{IntoResponse, Response};
use axum::Json;
use tokio::task;
use tracing::{debug, error, info};

use crate::cache::ExtensionCache;
use crate::client::{ClientFactory, DistributionClient, SourceClient};
use crate::error::{RegistryError, Result};
#[cfg(feature = "nats")]
use crate::events::{spawn_cache_invalidator, EventPublisher};
use crate::models::*;

/// Application state
#[derive(Clone)]
pub struct AppState {
    pub source_clients: Vec<Arc<dyn SourceClient>>,
    pub distribution_clients: Vec<Arc<dyn DistributionClient>>,
    pub cache: Arc<ExtensionCache>,
    pub start_time: Instant,
    #[cfg(feature = "nats")]
    pub events: Option<Arc<EventPublisher>>,
}

impl AppState {
    /// Create application state.
    ///
    /// `vault_url` enables vault:// token resolution (e.g. `Some("http://127.0.0.1:9094")`).
    /// Pass `None` to use filesystem-only token resolution.
    pub async fn new(config: crate::config::Config, vault_url: Option<&str>) -> Result<Self> {
        let (source_clients, distribution_clients) =
            ClientFactory::create_from_config(&config, vault_url).await?;

        let cache = Arc::new(ExtensionCache::new(
            config.cache.capacity,
            config.cache.ttl_seconds,
            config.cache.enable_metadata_cache,
            config.cache.enable_list_cache,
        ));

        Ok(Self {
            source_clients,
            distribution_clients,
            cache,
            start_time: Instant::now(),
            #[cfg(feature = "nats")]
            events: None,
        })
    }

    /// Attach a connected NatsBridge and spawn the cache invalidator
    /// subscriber.
    #[cfg(feature = "nats")]
    pub fn with_nats(mut self, bridge: Arc<platform_nats::NatsBridge>) -> Self {
        spawn_cache_invalidator(Arc::clone(&bridge), Arc::clone(&self.cache));
        self.events = Some(Arc::new(EventPublisher::new(bridge)));
        self
    }
}

/// List all extensions
///
/// Aggregates results from all source and distribution clients in parallel.
/// Uses fallback strategy if clients fail individually.
pub async fn list_extensions(
    State(state): State<Arc<AppState>>,
    Query(params): Query<ListParams>,
) -> Result<Json<Vec<Extension>>> {
    debug!("Listing extensions with params: {:?}", params);

    let cache_key = format!("list:{:?}:{:?}", params.extension_type, params.source);

    // Check cache
    if let Some(cached) = state.cache.get_list(&cache_key) {
        debug!("Cache hit for list: {}", cache_key);
        return Ok(Json(cached));
    }

    let mut all_extensions = Vec::new();

    // Collect futures for parallel execution
    let mut futures = Vec::new();

    // Spawn tasks for source clients (Git-based)
    for client in &state.source_clients {
        let client = Arc::clone(client);
        let ext_type = params.extension_type;
        futures.push(task::spawn(async move {
            client.list_extensions(ext_type).await
        }));
    }

    // Spawn tasks for distribution clients (OCI)
    for client in &state.distribution_clients {
        let client = Arc::clone(client);
        let ext_type = params.extension_type;
        futures.push(task::spawn(async move {
            client.list_extensions(ext_type).await
        }));
    }

    // Collect results from all parallel tasks
    for future in futures {
        match future.await {
            Ok(Ok(extensions)) => all_extensions.extend(extensions),
            Ok(Err(e)) => error!("Client list failed: {}", e),
            Err(e) => error!("Task panicked: {}", e),
        }
    }

    // Deduplicate extensions by name (prefer first occurrence)
    all_extensions.sort_by(|a, b| {
        a.identifier()
            .cmp(&b.identifier())
            .then_with(|| b.published_at.cmp(&a.published_at))
    });
    all_extensions.dedup_by(|a, b| a.identifier() == b.identifier());

    // Apply pagination
    let offset = params.offset.unwrap_or(0);
    let limit = params.limit.unwrap_or(100);
    let paginated: Vec<Extension> = all_extensions
        .into_iter()
        .skip(offset)
        .take(limit)
        .collect();

    // Update cache
    state.cache.put_list(cache_key, paginated.clone());

    Ok(Json(paginated))
}

/// Get specific extension
///
/// Uses fallback strategy: try source clients first, then distribution clients.
/// Returns first successful result.
pub async fn get_extension(
    State(state): State<Arc<AppState>>,
    Path((ext_type, name)): Path<(String, String)>,
) -> Result<Json<Extension>> {
    let extension_type: ExtensionType = ext_type.parse()?;
    debug!("Getting extension: {}/{}", extension_type, name);

    let cache_key = format!("{}/{}", extension_type, name);

    // Check cache
    if let Some(cached) = state.cache.get_metadata(&cache_key) {
        debug!("Cache hit for metadata: {}", cache_key);
        return Ok(Json(cached));
    }

    // Try source clients first
    for client in &state.source_clients {
        if let Ok(extension) = client.get_extension(extension_type, &name).await {
            state.cache.put_metadata(cache_key, extension.clone());
            return Ok(Json(extension));
        }
    }

    // Try distribution clients
    for client in &state.distribution_clients {
        if let Ok(extension) = client.get_extension(extension_type, &name).await {
            state.cache.put_metadata(cache_key, extension.clone());
            return Ok(Json(extension));
        }
    }

    Err(RegistryError::NotFound(format!(
        "{}/{}",
        extension_type, name
    )))
}

/// List versions for extension
///
/// Aggregates versions from all clients in parallel.
/// Deduplicates by version string and sorts by published_at descending.
pub async fn list_versions(
    State(state): State<Arc<AppState>>,
    Path((ext_type, name)): Path<(String, String)>,
) -> Result<Json<Vec<ExtensionVersion>>> {
    let extension_type: ExtensionType = ext_type.parse()?;
    debug!("Listing versions for: {}/{}", extension_type, name);

    let cache_key = format!("{}/{}/versions", extension_type, name);

    // Check cache
    if let Some(cached) = state.cache.get_versions(&cache_key) {
        debug!("Cache hit for versions: {}", cache_key);
        let versions: Vec<ExtensionVersion> = cached
            .iter()
            .map(|v| ExtensionVersion {
                version: v.clone(),
                published_at: chrono::Utc::now(),
                download_url: None,
                checksum: None,
                size: None,
            })
            .collect();
        return Ok(Json(versions));
    }

    let mut all_versions = Vec::new();
    let mut futures = Vec::new();

    // Spawn tasks for source clients
    for client in &state.source_clients {
        let client = Arc::clone(client);
        let name_clone = name.clone();
        futures.push(task::spawn(async move {
            client.list_versions(extension_type, &name_clone).await
        }));
    }

    // Spawn tasks for distribution clients
    for client in &state.distribution_clients {
        let client = Arc::clone(client);
        let name_clone = name.clone();
        futures.push(task::spawn(async move {
            client.list_versions(extension_type, &name_clone).await
        }));
    }

    // Collect results from all parallel tasks
    for future in futures {
        match future.await {
            Ok(Ok(versions)) => all_versions.extend(versions),
            Ok(Err(e)) => error!("Client list_versions failed: {}", e),
            Err(e) => error!("Task panicked: {}", e),
        }
    }

    if all_versions.is_empty() {
        return Err(RegistryError::NotFound(format!(
            "{}/{}",
            extension_type, name
        )));
    }

    // Deduplicate by version string (prefer first occurrence)
    all_versions.sort_by(|a, b| b.published_at.cmp(&a.published_at));
    all_versions.dedup_by(|a, b| a.version == b.version);

    // Cache version strings
    let version_strings: Vec<String> = all_versions.iter().map(|v| v.version.clone()).collect();
    state.cache.put_versions(cache_key, version_strings);

    Ok(Json(all_versions))
}

/// Download extension
///
/// Uses fallback strategy: try source clients first, then distribution clients.
/// Returns first successful download.
pub async fn download_extension(
    State(state): State<Arc<AppState>>,
    Path((ext_type, name, version)): Path<(String, String, String)>,
) -> Result<Response> {
    let extension_type: ExtensionType = ext_type.parse()?;
    info!(
        "Downloading extension: {}/{} version {}",
        extension_type, name, version
    );

    // Try source clients first
    for client in &state.source_clients {
        if let Ok(data) = client
            .download_extension(extension_type, &name, &version)
            .await
        {
            #[cfg(feature = "nats")]
            if let Some(events) = &state.events {
                let events = Arc::clone(events);
                let (ty, n, v) = (extension_type, name.clone(), version.clone());
                tokio::spawn(async move { events.publish_installed(ty, &n, &v).await });
            }
            return Ok((
                StatusCode::OK,
                [(header::CONTENT_TYPE, "application/octet-stream")],
                data,
            )
                .into_response());
        }
    }

    // Try distribution clients
    for client in &state.distribution_clients {
        if let Ok(data) = client
            .download_extension(extension_type, &name, &version)
            .await
        {
            #[cfg(feature = "nats")]
            if let Some(events) = &state.events {
                let events = Arc::clone(events);
                let (ty, n, v) = (extension_type, name.clone(), version.clone());
                tokio::spawn(async move { events.publish_installed(ty, &n, &v).await });
            }
            return Ok((
                StatusCode::OK,
                [(header::CONTENT_TYPE, "application/octet-stream")],
                data,
            )
                .into_response());
        }
    }

    Err(RegistryError::NotFound(format!(
        "{}/{} version {}",
        extension_type, name, version
    )))
}

/// Search extensions
pub async fn search_extensions(
    State(state): State<Arc<AppState>>,
    Query(params): Query<SearchParams>,
) -> Result<Json<Vec<Extension>>> {
    debug!("Searching extensions with query: {:?}", params);

    // Get all extensions
    let list_params = ListParams {
        extension_type: params.extension_type,
        source: None,
        limit: None,
        offset: None,
    };

    let all_extensions = match list_extensions(State(state), Query(list_params)).await {
        Ok(Json(exts)) => exts,
        Err(e) => return Err(e),
    };

    // Filter by search query
    let query_lower = params.q.to_lowercase();
    let filtered: Vec<Extension> = all_extensions
        .into_iter()
        .filter(|ext| {
            ext.name.to_lowercase().contains(&query_lower)
                || ext.description.to_lowercase().contains(&query_lower)
        })
        .take(params.limit.unwrap_or(50))
        .collect();

    Ok(Json(filtered))
}

/// Health check
///
/// Checks health of all configured backends in parallel.
/// Returns "healthy" if all backends are healthy, "degraded" if at least one is
/// healthy, and "unhealthy" if all backends are down.
pub async fn health_check(State(state): State<Arc<AppState>>) -> Json<HealthResponse> {
    let mut futures = Vec::new();

    // Spawn health check tasks for source clients
    for client in &state.source_clients {
        let client = Arc::clone(client);
        futures.push(task::spawn(async move {
            (client.backend_id().to_string(), client.health_check().await)
        }));
    }

    // Spawn health check tasks for distribution clients
    for client in &state.distribution_clients {
        let client = Arc::clone(client);
        futures.push(task::spawn(async move {
            (client.backend_id().to_string(), client.health_check().await)
        }));
    }

    // Collect health status from all clients
    let mut healthy_count = 0;
    let mut unhealthy_count = 0;

    for future in futures {
        match future.await {
            Ok((backend_id, Ok(()))) => {
                debug!("Backend {} is healthy", backend_id);
                healthy_count += 1;
            }
            Ok((backend_id, Err(e))) => {
                error!("Backend {} health check failed: {}", backend_id, e);
                unhealthy_count += 1;
            }
            Err(e) => {
                error!("Health check task panicked: {}", e);
                unhealthy_count += 1;
            }
        }
    }

    let total = healthy_count + unhealthy_count;
    let status = if total == 0 {
        "unhealthy".to_string()
    } else if unhealthy_count == 0 {
        "healthy".to_string()
    } else if healthy_count > 0 {
        "degraded".to_string()
    } else {
        "unhealthy".to_string()
    };

    Json(HealthResponse {
        status,
        version: env!("CARGO_PKG_VERSION").to_string(),
        uptime: state.start_time.elapsed().as_secs(),
        backends: BackendHealth {
            gitea: BackendStatus {
                enabled: !state.source_clients.is_empty(),
                healthy: healthy_count > 0,
                error: if unhealthy_count > 0 {
                    Some(format!("{} backends failed", unhealthy_count))
                } else {
                    None
                },
            },
            oci: BackendStatus {
                enabled: !state.distribution_clients.is_empty(),
                healthy: healthy_count > 0,
                error: if unhealthy_count > 0 {
                    Some(format!("{} backends failed", unhealthy_count))
                } else {
                    None
                },
            },
        },
    })
}

/// Metrics endpoint
pub async fn metrics() -> String {
    "# Metrics endpoint\n# Metrics collection not implemented\n".to_string()
}

/// Cache stats endpoint
pub async fn cache_stats(State(state): State<Arc<AppState>>) -> Json<crate::cache::CacheStats> {
    Json(state.cache.stats())
}
