//! HTTP Gateway - Web access to Synor Storage
//!
//! Provides HTTP endpoints for accessing content stored on Synor.
//! Makes decentralized storage accessible from any web browser.
//!
//! # URL Patterns
//!
//! - `GET /synor1abc...xyz` - Fetch content by CID
//! - `GET /synor1abc...xyz/path/to/file` - Fetch file from directory CID
//! - `HEAD /synor1abc...xyz` - Check if content exists
//! - `POST /upload` - Upload content (returns CID)

mod handler;
mod resolver;
mod cache;

pub use handler::GatewayHandler;
pub use resolver::ContentResolver;
pub use cache::GatewayCache;

use crate::cid::ContentId;
use crate::error::{Error, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;

/// Gateway configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GatewayConfig {
    /// HTTP listen address
    pub listen_addr: String,
    /// Maximum content size to serve (bytes)
    pub max_content_size: u64,
    /// Cache size in bytes
    pub cache_size: u64,
    /// Request timeout in seconds
    pub timeout_secs: u64,
    /// Enable upload endpoint
    pub enable_upload: bool,
    /// CORS allowed origins
    pub cors_origins: Vec<String>,
    /// Storage node endpoints for content retrieval
    pub storage_nodes: Vec<String>,
    /// Rate limit (requests per minute per IP)
    pub rate_limit: u32,
}

impl Default for GatewayConfig {
    fn default() -> Self {
        Self {
            listen_addr: "0.0.0.0:8080".to_string(),
            max_content_size: 100 * 1024 * 1024, // 100 MB
            cache_size: 1024 * 1024 * 1024,      // 1 GB
            timeout_secs: 60,
            enable_upload: false, // Disabled by default for public gateways
            cors_origins: vec!["*".to_string()],
            storage_nodes: vec![],
            rate_limit: 100,
        }
    }
}

/// HTTP Gateway service
pub struct Gateway {
    /// Configuration
    config: GatewayConfig,
    /// Content resolver
    resolver: Arc<ContentResolver>,
    /// Response cache
    cache: Arc<RwLock<GatewayCache>>,
    /// Gateway statistics
    stats: Arc<RwLock<GatewayStats>>,
    /// Running state
    running: Arc<std::sync::atomic::AtomicBool>,
}

/// Gateway statistics
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GatewayStats {
    /// Total requests handled
    pub total_requests: u64,
    /// Successful requests
    pub successful_requests: u64,
    /// Failed requests
    pub failed_requests: u64,
    /// Cache hits
    pub cache_hits: u64,
    /// Cache misses
    pub cache_misses: u64,
    /// Bytes served
    pub bytes_served: u64,
    /// Upload requests (if enabled)
    pub upload_requests: u64,
    /// Unique CIDs requested
    pub unique_cids: u64,
}

impl Gateway {
    /// Create a new gateway
    pub fn new(config: GatewayConfig) -> Self {
        let resolver = Arc::new(ContentResolver::new(config.storage_nodes.clone()));
        let cache = Arc::new(RwLock::new(GatewayCache::new(config.cache_size)));

        Self {
            config,
            resolver,
            cache,
            stats: Arc::new(RwLock::new(GatewayStats::default())),
            running: Arc::new(std::sync::atomic::AtomicBool::new(false)),
        }
    }

    /// Start the gateway server
    pub async fn start(&self) -> Result<()> {
        self.running
            .store(true, std::sync::atomic::Ordering::SeqCst);

        // TODO: Start HTTP server using axum/actix-web/warp
        // Routes:
        // - GET /{cid} -> fetch content
        // - HEAD /{cid} -> check existence
        // - POST /upload -> upload content (if enabled)
        // - GET /stats -> gateway statistics

        Ok(())
    }

    /// Stop the gateway
    pub async fn stop(&self) {
        self.running
            .store(false, std::sync::atomic::Ordering::SeqCst);
    }

    /// Check if gateway is running
    pub fn is_running(&self) -> bool {
        self.running.load(std::sync::atomic::Ordering::SeqCst)
    }

    /// Get gateway statistics
    pub async fn stats(&self) -> GatewayStats {
        self.stats.read().await.clone()
    }

    /// Fetch content by CID
    pub async fn fetch(&self, cid: &ContentId) -> Result<GatewayResponse> {
        // Check cache first
        {
            let cache = self.cache.read().await;
            if let Some(cached) = cache.get(cid) {
                let mut stats = self.stats.write().await;
                stats.cache_hits += 1;
                return Ok(cached);
            }
        }

        // Resolve from storage network
        let content = self.resolver.resolve(cid).await?;

        // Determine MIME type
        let mime_type = detect_mime_type(&content);

        let response = GatewayResponse {
            cid: cid.clone(),
            content,
            mime_type,
            size: cid.size,
        };

        // Cache the response
        {
            let mut cache = self.cache.write().await;
            cache.put(cid.clone(), response.clone());
        }

        // Update stats
        {
            let mut stats = self.stats.write().await;
            stats.cache_misses += 1;
            stats.bytes_served += response.size;
        }

        Ok(response)
    }
}

/// Gateway response
#[derive(Debug, Clone)]
pub struct GatewayResponse {
    /// Content ID
    pub cid: ContentId,
    /// Content bytes
    pub content: Vec<u8>,
    /// MIME type
    pub mime_type: String,
    /// Content size
    pub size: u64,
}

/// Detect MIME type from content
fn detect_mime_type(content: &[u8]) -> String {
    // Magic byte detection for common types
    if content.starts_with(b"\x89PNG\r\n\x1a\n") {
        "image/png".to_string()
    } else if content.starts_with(b"\xff\xd8\xff") {
        "image/jpeg".to_string()
    } else if content.starts_with(b"GIF87a") || content.starts_with(b"GIF89a") {
        "image/gif".to_string()
    } else if content.starts_with(b"RIFF") && content.len() > 12 && &content[8..12] == b"WEBP" {
        "image/webp".to_string()
    } else if content.starts_with(b"PK\x03\x04") {
        // ZIP-based formats
        "application/zip".to_string()
    } else if content.starts_with(b"%PDF") {
        "application/pdf".to_string()
    } else if content.starts_with(b"<!DOCTYPE html") || content.starts_with(b"<html") {
        "text/html".to_string()
    } else if content.starts_with(b"<?xml") {
        "application/xml".to_string()
    } else if content.starts_with(b"{") || content.starts_with(b"[") {
        "application/json".to_string()
    } else if content.iter().take(1024).all(|&b| b.is_ascii()) {
        "text/plain".to_string()
    } else {
        "application/octet-stream".to_string()
    }
}

/// Content type for directories (DAG structures)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DirectoryListing {
    /// Directory entries
    pub entries: Vec<DirectoryEntry>,
}

/// Single directory entry
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DirectoryEntry {
    /// Entry name
    pub name: String,
    /// CID of the entry
    pub cid: ContentId,
    /// Size in bytes
    pub size: u64,
    /// True if this is a directory
    pub is_directory: bool,
}
