//! Package Registry Server for Lux
//!
//! Provides a central repository for sharing Lux packages.
//! The registry serves package metadata and tarballs via HTTP.

use std::collections::HashMap;
use std::fs;
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
use std::thread;

/// Package metadata stored in the registry
#[derive(Debug, Clone)]
pub struct PackageMetadata {
    pub name: String,
    pub version: String,
    pub description: String,
    pub authors: Vec<String>,
    pub license: Option<String>,
    pub repository: Option<String>,
    pub keywords: Vec<String>,
    pub dependencies: HashMap<String, String>,
    pub checksum: String,
    pub published_at: String,
}

/// A version entry for a package
#[derive(Debug, Clone)]
pub struct VersionEntry {
    pub version: String,
    pub checksum: String,
    pub published_at: String,
    pub yanked: bool,
}

/// Package index entry (all versions of a package)
#[derive(Debug, Clone)]
pub struct PackageIndex {
    pub name: String,
    pub description: String,
    pub versions: Vec<VersionEntry>,
    pub latest_version: String,
}

/// The package registry
pub struct Registry {
    /// Base directory for storing packages
    storage_dir: PathBuf,
    /// In-memory index of all packages
    index: Arc<RwLock<HashMap<String, PackageIndex>>>,
}

impl Registry {
    /// Create a new registry with the given storage directory
    pub fn new(storage_dir: &Path) -> Self {
        let registry = Self {
            storage_dir: storage_dir.to_path_buf(),
            index: Arc::new(RwLock::new(HashMap::new())),
        };
        registry.load_index();
        registry
    }

    /// Load the package index from disk
    fn load_index(&self) {
        let index_path = self.storage_dir.join("index.json");
        if !index_path.exists() {
            return;
        }

        if let Ok(content) = fs::read_to_string(&index_path) {
            if let Ok(index) = parse_index_json(&content) {
                let mut idx = self.index.write().unwrap();
                *idx = index;
            }
        }
    }

    /// Save the package index to disk
    fn save_index(&self) {
        let index_path = self.storage_dir.join("index.json");
        let idx = self.index.read().unwrap();
        let json = format_index_json(&idx);
        fs::write(&index_path, json).ok();
    }

    /// Publish a new package version
    pub fn publish(&self, metadata: PackageMetadata, tarball: &[u8]) -> Result<(), String> {
        // Validate package name
        if !is_valid_package_name(&metadata.name) {
            return Err("Invalid package name. Use lowercase letters, numbers, and hyphens.".to_string());
        }

        // Create package directory
        let pkg_dir = self.storage_dir.join("packages").join(&metadata.name);
        fs::create_dir_all(&pkg_dir)
            .map_err(|e| format!("Failed to create package directory: {}", e))?;

        // Write tarball
        let tarball_path = pkg_dir.join(format!("{}-{}.tar.gz", metadata.name, metadata.version));
        fs::write(&tarball_path, tarball)
            .map_err(|e| format!("Failed to write package tarball: {}", e))?;

        // Write metadata
        let meta_path = pkg_dir.join(format!("{}-{}.json", metadata.name, metadata.version));
        let meta_json = format_metadata_json(&metadata);
        fs::write(&meta_path, meta_json)
            .map_err(|e| format!("Failed to write package metadata: {}", e))?;

        // Update index
        {
            let mut idx = self.index.write().unwrap();
            let entry = idx.entry(metadata.name.clone()).or_insert_with(|| PackageIndex {
                name: metadata.name.clone(),
                description: metadata.description.clone(),
                versions: Vec::new(),
                latest_version: String::new(),
            });

            // Check if version already exists
            if entry.versions.iter().any(|v| v.version == metadata.version) {
                return Err(format!("Version {} already exists", metadata.version));
            }

            entry.versions.push(VersionEntry {
                version: metadata.version.clone(),
                checksum: metadata.checksum.clone(),
                published_at: metadata.published_at.clone(),
                yanked: false,
            });

            // Update latest version (simple comparison for now)
            entry.latest_version = metadata.version.clone();
            entry.description = metadata.description.clone();
        }

        self.save_index();
        Ok(())
    }

    /// Get package metadata
    pub fn get_metadata(&self, name: &str, version: &str) -> Option<PackageMetadata> {
        let meta_path = self.storage_dir
            .join("packages")
            .join(name)
            .join(format!("{}-{}.json", name, version));

        if let Ok(content) = fs::read_to_string(&meta_path) {
            parse_metadata_json(&content)
        } else {
            None
        }
    }

    /// Get package tarball
    pub fn get_tarball(&self, name: &str, version: &str) -> Option<Vec<u8>> {
        let tarball_path = self.storage_dir
            .join("packages")
            .join(name)
            .join(format!("{}-{}.tar.gz", name, version));

        fs::read(&tarball_path).ok()
    }

    /// Search packages
    pub fn search(&self, query: &str) -> Vec<PackageIndex> {
        let idx = self.index.read().unwrap();
        let query_lower = query.to_lowercase();

        idx.values()
            .filter(|pkg| {
                pkg.name.to_lowercase().contains(&query_lower) ||
                pkg.description.to_lowercase().contains(&query_lower)
            })
            .cloned()
            .collect()
    }

    /// List all packages
    pub fn list_all(&self) -> Vec<PackageIndex> {
        let idx = self.index.read().unwrap();
        idx.values().cloned().collect()
    }

    /// Get package index entry
    pub fn get_package(&self, name: &str) -> Option<PackageIndex> {
        let idx = self.index.read().unwrap();
        idx.get(name).cloned()
    }
}

/// HTTP Registry Server
pub struct RegistryServer {
    registry: Arc<Registry>,
    bind_addr: String,
}

impl RegistryServer {
    /// Create a new registry server
    pub fn new(storage_dir: &Path, bind_addr: &str) -> Self {
        Self {
            registry: Arc::new(Registry::new(storage_dir)),
            bind_addr: bind_addr.to_string(),
        }
    }

    /// Run the server
    pub fn run(&self) -> Result<(), String> {
        let listener = TcpListener::bind(&self.bind_addr)
            .map_err(|e| format!("Failed to bind to {}: {}", self.bind_addr, e))?;

        println!("Lux Package Registry running at http://{}", self.bind_addr);
        println!("Storage directory: {}", self.registry.storage_dir.display());
        println!();
        println!("Endpoints:");
        println!("  GET  /api/v1/packages              - List all packages");
        println!("  GET  /api/v1/packages/:name        - Get package info");
        println!("  GET  /api/v1/packages/:name/:ver   - Get version metadata");
        println!("  GET  /api/v1/download/:name/:ver   - Download package tarball");
        println!("  GET  /api/v1/search?q=query        - Search packages");
        println!("  POST /api/v1/publish               - Publish a package");
        println!();

        for stream in listener.incoming() {
            match stream {
                Ok(stream) => {
                    let registry = Arc::clone(&self.registry);
                    thread::spawn(move || {
                        handle_request(stream, &registry);
                    });
                }
                Err(e) => {
                    eprintln!("Connection error: {}", e);
                }
            }
        }

        Ok(())
    }
}

/// Handle an HTTP request
fn handle_request(mut stream: TcpStream, registry: &Registry) {
    let mut buffer = [0; 8192];
    let bytes_read = match stream.read(&mut buffer) {
        Ok(n) => n,
        Err(_) => return,
    };

    let request = String::from_utf8_lossy(&buffer[..bytes_read]);
    let lines: Vec<&str> = request.lines().collect();

    if lines.is_empty() {
        return;
    }

    let parts: Vec<&str> = lines[0].split_whitespace().collect();
    if parts.len() < 2 {
        return;
    }

    let method = parts[0];
    let path = parts[1];

    // Parse path and query string
    let (path, query) = if let Some(q_pos) = path.find('?') {
        (&path[..q_pos], Some(&path[q_pos + 1..]))
    } else {
        (path, None)
    };

    let response = match (method, path) {
        ("GET", "/") => {
            html_response(200, r#"
                <!DOCTYPE html>
                <html>
                <head><title>Lux Package Registry</title></head>
                <body>
                    <h1>Lux Package Registry</h1>
                    <p>Welcome to the Lux package registry.</p>
                    <h2>API Endpoints</h2>
                    <ul>
                        <li>GET /api/v1/packages - List all packages</li>
                        <li>GET /api/v1/packages/:name - Get package info</li>
                        <li>GET /api/v1/packages/:name/:version - Get version metadata</li>
                        <li>GET /api/v1/download/:name/:version - Download package</li>
                        <li>GET /api/v1/search?q=query - Search packages</li>
                    </ul>
                </body>
                </html>
            "#)
        }

        ("GET", "/api/v1/packages") => {
            let packages = registry.list_all();
            let json = format_packages_list_json(&packages);
            json_response(200, &json)
        }

        ("GET", path) if path.starts_with("/api/v1/packages/") => {
            let rest = &path[17..]; // Remove "/api/v1/packages/"
            let parts: Vec<&str> = rest.split('/').collect();

            match parts.len() {
                1 => {
                    // Get package info
                    if let Some(pkg) = registry.get_package(parts[0]) {
                        let json = format_package_json(&pkg);
                        json_response(200, &json)
                    } else {
                        json_response(404, r#"{"error": "Package not found"}"#)
                    }
                }
                2 => {
                    // Get version metadata
                    if let Some(meta) = registry.get_metadata(parts[0], parts[1]) {
                        let json = format_metadata_json(&meta);
                        json_response(200, &json)
                    } else {
                        json_response(404, r#"{"error": "Version not found"}"#)
                    }
                }
                _ => json_response(400, r#"{"error": "Invalid path"}"#)
            }
        }

        ("GET", path) if path.starts_with("/api/v1/download/") => {
            let rest = &path[17..]; // Remove "/api/v1/download/"
            let parts: Vec<&str> = rest.split('/').collect();

            if parts.len() == 2 {
                if let Some(tarball) = registry.get_tarball(parts[0], parts[1]) {
                    tarball_response(&tarball)
                } else {
                    json_response(404, r#"{"error": "Package not found"}"#)
                }
            } else {
                json_response(400, r#"{"error": "Invalid path"}"#)
            }
        }

        ("GET", "/api/v1/search") => {
            let q = query
                .and_then(|qs| parse_query_string(qs).get("q").cloned())
                .unwrap_or_default();

            let results = registry.search(&q);
            let json = format_packages_list_json(&results);
            json_response(200, &json)
        }

        ("POST", "/api/v1/publish") => {
            // Find content length
            let content_length: usize = lines.iter()
                .find(|l| l.to_lowercase().starts_with("content-length:"))
                .and_then(|l| l.split(':').nth(1))
                .and_then(|s| s.trim().parse().ok())
                .unwrap_or(0);

            // Find body start
            let body_start = request.find("\r\n\r\n")
                .map(|i| i + 4)
                .unwrap_or(bytes_read);

            // For now, return a message about publishing
            // Real implementation would parse multipart form data
            json_response(200, &format!(
                r#"{{"message": "Publish endpoint ready", "content_length": {}}}"#,
                content_length
            ))
        }

        _ => {
            json_response(404, r#"{"error": "Not found"}"#)
        }
    };

    stream.write_all(response.as_bytes()).ok();
}

/// Create an HTML response
fn html_response(status: u16, body: &str) -> String {
    let status_text = match status {
        200 => "OK",
        400 => "Bad Request",
        404 => "Not Found",
        500 => "Internal Server Error",
        _ => "Unknown",
    };

    format!(
        "HTTP/1.1 {} {}\r\nContent-Type: text/html\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
        status, status_text, body.len(), body
    )
}

/// Create a JSON response
fn json_response(status: u16, body: &str) -> String {
    let status_text = match status {
        200 => "OK",
        400 => "Bad Request",
        404 => "Not Found",
        500 => "Internal Server Error",
        _ => "Unknown",
    };

    format!(
        "HTTP/1.1 {} {}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
        status, status_text, body.len(), body
    )
}

/// Create a tarball response
fn tarball_response(data: &[u8]) -> String {
    format!(
        "HTTP/1.1 200 OK\r\nContent-Type: application/gzip\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
        data.len()
    )
}

/// Validate package name
fn is_valid_package_name(name: &str) -> bool {
    !name.is_empty() &&
    name.len() <= 64 &&
    name.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-' || c == '_') &&
    name.chars().next().map(|c| c.is_ascii_lowercase()).unwrap_or(false)
}

/// Parse query string into key-value pairs
fn parse_query_string(qs: &str) -> HashMap<String, String> {
    let mut params = HashMap::new();
    for part in qs.split('&') {
        if let Some(eq_pos) = part.find('=') {
            let key = &part[..eq_pos];
            let value = &part[eq_pos + 1..];
            params.insert(
                urlldecode(key),
                urlldecode(value),
            );
        }
    }
    params
}

/// Simple URL decoding
fn urlldecode(s: &str) -> String {
    let mut result = String::new();
    let mut chars = s.chars().peekable();
    while let Some(c) = chars.next() {
        if c == '%' {
            let hex: String = chars.by_ref().take(2).collect();
            if let Ok(byte) = u8::from_str_radix(&hex, 16) {
                result.push(byte as char);
            }
        } else if c == '+' {
            result.push(' ');
        } else {
            result.push(c);
        }
    }
    result
}

// JSON formatting helpers

fn format_metadata_json(meta: &PackageMetadata) -> String {
    let deps: Vec<String> = meta.dependencies.iter()
        .map(|(k, v)| format!(r#""{}": "{}""#, k, v))
        .collect();

    let authors: Vec<String> = meta.authors.iter()
        .map(|a| format!(r#""{}""#, a))
        .collect();

    let keywords: Vec<String> = meta.keywords.iter()
        .map(|k| format!(r#""{}""#, k))
        .collect();

    format!(
        r#"{{
  "name": "{}",
  "version": "{}",
  "description": "{}",
  "authors": [{}],
  "license": {},
  "repository": {},
  "keywords": [{}],
  "dependencies": {{{}}},
  "checksum": "{}",
  "published_at": "{}"
}}"#,
        meta.name,
        meta.version,
        escape_json(&meta.description),
        authors.join(", "),
        meta.license.as_ref().map(|l| format!(r#""{}""#, l)).unwrap_or("null".to_string()),
        meta.repository.as_ref().map(|r| format!(r#""{}""#, r)).unwrap_or("null".to_string()),
        keywords.join(", "),
        deps.join(", "),
        meta.checksum,
        meta.published_at,
    )
}

fn format_package_json(pkg: &PackageIndex) -> String {
    let versions: Vec<String> = pkg.versions.iter()
        .map(|v| format!(
            r#"{{"version": "{}", "checksum": "{}", "published_at": "{}", "yanked": {}}}"#,
            v.version, v.checksum, v.published_at, v.yanked
        ))
        .collect();

    format!(
        r#"{{
  "name": "{}",
  "description": "{}",
  "latest_version": "{}",
  "versions": [{}]
}}"#,
        pkg.name,
        escape_json(&pkg.description),
        pkg.latest_version,
        versions.join(", ")
    )
}

fn format_packages_list_json(packages: &[PackageIndex]) -> String {
    let items: Vec<String> = packages.iter()
        .map(|pkg| format!(
            r#"{{"name": "{}", "description": "{}", "latest_version": "{}"}}"#,
            pkg.name,
            escape_json(&pkg.description),
            pkg.latest_version
        ))
        .collect();

    format!(r#"{{"packages": [{}]}}"#, items.join(", "))
}

fn format_index_json(index: &HashMap<String, PackageIndex>) -> String {
    let items: Vec<String> = index.values()
        .map(|pkg| format_package_json(pkg))
        .collect();

    format!(r#"{{"packages": [{}]}}"#, items.join(",\n"))
}

fn parse_index_json(content: &str) -> Result<HashMap<String, PackageIndex>, String> {
    // Simple JSON parsing for the index
    // In production, would use serde_json
    let mut index = HashMap::new();

    // Basic parsing - find package names and latest versions
    // This is a simplified parser for the index format
    let content = content.trim();
    if !content.starts_with('{') || !content.ends_with('}') {
        return Err("Invalid JSON format".to_string());
    }

    // For now, return empty index if parsing fails
    // Real implementation would properly parse JSON
    Ok(index)
}

fn parse_metadata_json(content: &str) -> Option<PackageMetadata> {
    // Simple JSON parsing for metadata
    // In production, would use serde_json
    let mut name = String::new();
    let mut version = String::new();
    let mut description = String::new();
    let mut checksum = String::new();
    let mut published_at = String::new();

    for line in content.lines() {
        let line = line.trim();
        if line.contains("\"name\":") {
            name = extract_json_string(line);
        } else if line.contains("\"version\":") {
            version = extract_json_string(line);
        } else if line.contains("\"description\":") {
            description = extract_json_string(line);
        } else if line.contains("\"checksum\":") {
            checksum = extract_json_string(line);
        } else if line.contains("\"published_at\":") {
            published_at = extract_json_string(line);
        }
    }

    if name.is_empty() || version.is_empty() {
        return None;
    }

    Some(PackageMetadata {
        name,
        version,
        description,
        authors: Vec::new(),
        license: None,
        repository: None,
        keywords: Vec::new(),
        dependencies: HashMap::new(),
        checksum,
        published_at,
    })
}

fn extract_json_string(line: &str) -> String {
    // Extract string value from "key": "value" format
    if let Some(colon) = line.find(':') {
        let value = line[colon + 1..].trim();
        let value = value.trim_start_matches('"');
        if let Some(end) = value.find('"') {
            return value[..end].to_string();
        }
    }
    String::new()
}

fn escape_json(s: &str) -> String {
    s.replace('\\', "\\\\")
     .replace('"', "\\\"")
     .replace('\n', "\\n")
     .replace('\r', "\\r")
     .replace('\t', "\\t")
}

/// Run the registry server (called from main)
pub fn run_registry_server(storage_dir: &str, bind_addr: &str) -> Result<(), String> {
    let storage_path = PathBuf::from(storage_dir);
    fs::create_dir_all(&storage_path)
        .map_err(|e| format!("Failed to create storage directory: {}", e))?;

    let server = RegistryServer::new(&storage_path, bind_addr);
    server.run()
}
