// Docker Package Scanner
// Scans docker-compose containers and converts them to package data

use anyhow::Result;
use archipelago_container::{ContainerRuntime as ContainerRuntimeTrait, ContainerState};
use std::collections::HashMap;
use std::sync::Arc;
use tracing::{debug, info};

use crate::data_model::{
    Description, InstalledPackageDataEntry, InterfaceAddress, Interfaces, MainInterface, Manifest,
    PackageDataEntry, PackageState, ServiceStatus, StaticFiles,
};

pub struct DockerPackageScanner {
    runtime: Arc<dyn ContainerRuntimeTrait>,
}

impl DockerPackageScanner {
    pub fn new(runtime: Arc<dyn ContainerRuntimeTrait>) -> Self {
        Self { runtime }
    }

    /// Scan Docker containers and convert to package data
    pub async fn scan_containers(&self) -> Result<HashMap<String, PackageDataEntry>> {
        let containers = match self.runtime.list_containers().await {
            Ok(c) => c,
            Err(e) => {
                debug!("Failed to list containers: {}", e);
                return Ok(HashMap::new());
            }
        };
        
        debug!("Found {} containers", containers.len());
        
        let mut packages = HashMap::new();
        
        // Backend services that should not appear as apps
        let excluded_services = [
            "btcpay-db",
            "nbxplorer",
            "mempool-db",
            "mempool-api",
            "penpot-postgres",
            "penpot-backend",
            "penpot-exporter",
            "penpot-valkey",
            "penpot-mailcatch",
            "immich_postgres",
            "immich_redis",
            "endurain-db",
            "nextcloud-db",
        ];
        
        // First pass: collect UI containers
        let mut ui_containers: HashMap<String, String> = HashMap::new();
        for container in &containers {
            if container.name.ends_with("-ui") {
                // Map fedimint-ui -> fedimint, lnd-ui -> lnd (normalize archy- prefix for lookup)
                let parent_app = container.name.strip_suffix("-ui").unwrap_or(&container.name);
                let canonical_id = parent_app
                    .strip_prefix("archy-")
                    .unwrap_or(parent_app)
                    .to_string();
                if !container.ports.is_empty() {
                    if let Some(ui_address) = extract_lan_address(&container.ports) {
                        ui_containers.insert(canonical_id, ui_address);
                    }
                }
            }
        }
        
        debug!("Found {} UI containers", ui_containers.len());
        
        for container in containers {
            // Extract app ID from container name
            // Support both archy-* containers (docker-compose) and plain names (manual)
            let app_id = if container.name.starts_with("archy-") {
                container.name.strip_prefix("archy-")
                    .unwrap_or(&container.name)
                    .to_string()
            } else {
                // Use the container name as-is for manually started containers
                container.name.clone()
            };

            // Normalize multi-container app IDs to their canonical names
            let app_id = match app_id.as_str() {
                "immich_server" => "immich".to_string(),
                _ => app_id,
            };
            
            // Skip backend services (databases, APIs, etc.)
            if excluded_services.contains(&app_id.as_str()) {
                debug!("Skipping backend service: {}", app_id);
                continue;
            }
            
            // Skip UI containers (they're merged with their parent apps)
            if app_id.ends_with("-ui") {
                debug!("Skipping UI container: {}", app_id);
                continue;
            }
            
            // Get metadata for this app
            let metadata = get_app_metadata(&app_id);
            
            // Check if this app has a separate UI container
            let lan_address = if let Some(ui_address) = ui_containers.get(&app_id) {
                debug!("Using UI container address for {}: {}", app_id, ui_address);
                Some(ui_address.clone())
            } else if app_id == "bitcoin-knots" {
                // Bitcoin UI runs on host network at port 8334
                debug!("Using bitcoin-ui for bitcoin-knots: http://localhost:8334");
                Some("http://localhost:8334".to_string())
            } else if app_id == "lnd" {
                // LND UI runs on host network at port 8081
                debug!("Using lnd-ui for lnd: http://localhost:8081");
                Some("http://localhost:8081".to_string())
            } else if app_id == "tailscale" {
                // Tailscale uses host networking, so no port mappings
                // But web UI is always on port 8240
                debug!("Tailscale detected, using port 8240");
                Some("http://localhost:8240".to_string())
            } else if app_id == "fedimint" {
                // Fedimint built-in Guardian UI on port 8175
                debug!("Using fedimint built-in Guardian UI: http://localhost:8175");
                Some("http://localhost:8175".to_string())
            } else if app_id == "mempool-electrs" || app_id == "electrs" {
                // Electrs UI runs on host at port 50002
                debug!("Using electrs-ui for mempool-electrs: http://localhost:50002");
                Some("http://localhost:50002".to_string())
            } else {
                // Extract port from the main container
                extract_lan_address(&container.ports)
            };
            
            debug!("Container {}: ports={:?}, lan_address={:?}", app_id, container.ports, lan_address);
            
            // Convert container state to package/service state
            let (package_state, service_status) = convert_state(&container.state);
            
            let tor_address = read_tor_address(&app_id);

            let package = PackageDataEntry {
                state: package_state.clone(),
                static_files: StaticFiles {
                    license: "MIT".to_string(),
                    instructions: metadata.description.clone(),
                    icon: metadata.icon.clone(),
                },
                manifest: Manifest {
                    id: app_id.clone(),
                    title: metadata.title.clone(),
                    version: "1.0.0".to_string(),
                    description: Description {
                        short: metadata.description.clone(),
                        long: metadata.description.clone(),
                    },
                    release_notes: "Docker container".to_string(),
                    license: "MIT".to_string(),
                    wrapper_repo: metadata.repo.clone(),
                    upstream_repo: metadata.repo.clone(),
                    support_site: metadata.repo.clone(),
                    marketing_site: metadata.repo.clone(),
                    donation_url: None,
                    author: Some("Archipelago".to_string()),
                    website: lan_address.clone(),
                    interfaces: if lan_address.is_some() || tor_address.is_some() {
                        Some(Interfaces {
                            main: Some(MainInterface {
                                ui: Some("true".to_string()),
                                tor_config: tor_address.clone(),
                                lan_config: None,
                            }),
                        })
                    } else {
                        None
                    },
                },
                installed: Some(InstalledPackageDataEntry {
                    current_dependents: HashMap::new(),
                    current_dependencies: HashMap::new(),
                    last_backup: None,
                    interface_addresses: if lan_address.is_some() || tor_address.is_some() {
                        let mut addresses = HashMap::new();
                        // Only include tor_address if we have a real v3 .onion (not placeholder)
                        let tor = tor_address
                            .filter(|s| is_real_onion_address(s))
                            .unwrap_or_default();
                        addresses.insert(
                            "main".to_string(),
                            InterfaceAddress {
                                tor_address: tor,
                                lan_address: lan_address,
                            },
                        );
                        addresses
                    } else {
                        HashMap::new()
                    },
                    status: service_status,
                }),
                install_progress: None,
            };
            
            packages.insert(app_id.clone(), package);
            info!("Detected container: {} ({})", metadata.title, package_state_str(&package_state));
        }
        
        // Virtual app: Indeehub (opens external URL, no container required)
        if !packages.contains_key("indeedhub") {
            let metadata = get_app_metadata("indeedhub");
            let lan_address = Some("https://archipelago.indeehub.studio".to_string());
            let virtual_pkg = PackageDataEntry {
                state: PackageState::Running,
                static_files: StaticFiles {
                    license: "MIT".to_string(),
                    instructions: metadata.description.clone(),
                    icon: metadata.icon.clone(),
                },
                manifest: Manifest {
                    id: "indeedhub".to_string(),
                    title: metadata.title.clone(),
                    version: "0.1.0".to_string(),
                    description: Description {
                        short: metadata.description.clone(),
                        long: metadata.description.clone(),
                    },
                    release_notes: "Virtual app (opens archipelago.indeehub.studio)".to_string(),
                    license: "MIT".to_string(),
                    wrapper_repo: metadata.repo.clone(),
                    upstream_repo: metadata.repo.clone(),
                    support_site: metadata.repo.clone(),
                    marketing_site: metadata.repo.clone(),
                    donation_url: None,
                    author: Some("Indeehub Team".to_string()),
                    website: lan_address.clone(),
                    interfaces: Some(Interfaces {
                        main: Some(MainInterface {
                            ui: Some("true".to_string()),
                            tor_config: None,
                            lan_config: None,
                        }),
                    }),
                },
                installed: Some(InstalledPackageDataEntry {
                    current_dependents: HashMap::new(),
                    current_dependencies: HashMap::new(),
                    last_backup: None,
                    interface_addresses: {
                        let mut addresses = HashMap::new();
                        addresses.insert(
                            "main".to_string(),
                            InterfaceAddress {
                                tor_address: String::new(),
                                lan_address: lan_address,
                            },
                        );
                        addresses
                    },
                    status: ServiceStatus::Running,
                }),
                install_progress: None,
            };
            packages.insert("indeedhub".to_string(), virtual_pkg);
            info!("Virtual app: Indeehub (archipelago.indeehub.studio)");
        }
        
        Ok(packages)
    }
}

struct AppMetadata {
    title: String,
    description: String,
    icon: String,
    repo: String,
}

fn get_app_metadata(app_id: &str) -> AppMetadata {
    match app_id {
        "bitcoin" | "bitcoin-core" | "bitcoin-knots" => AppMetadata {
            title: "Bitcoin Knots".to_string(),
            description: "Full Bitcoin node implementation".to_string(),
            icon: "/assets/img/app-icons/bitcoin-knots.webp".to_string(),
            repo: "https://github.com/bitcoinknots/bitcoin".to_string(),
        },
        "btcpay" | "btcpay-server" | "btcpayserver" => AppMetadata {
            title: "BTCPay Server".to_string(),
            description: "Self-hosted Bitcoin payment processor".to_string(),
            icon: "/assets/img/app-icons/btcpay-server.png".to_string(),
            repo: "https://github.com/btcpayserver/btcpayserver".to_string(),
        },
        "homeassistant" | "home-assistant" => AppMetadata {
            title: "Home Assistant".to_string(),
            description: "Open source home automation platform".to_string(),
            icon: "/assets/img/app-icons/homeassistant.png".to_string(),
            repo: "https://github.com/home-assistant/core".to_string(),
        },
        "grafana" => AppMetadata {
            title: "Grafana".to_string(),
            description: "Analytics and monitoring platform".to_string(),
            icon: "/assets/img/app-icons/grafana.png".to_string(),
            repo: "https://github.com/grafana/grafana".to_string(),
        },
        "endurain" => AppMetadata {
            title: "Endurain".to_string(),
            description: "Self-hosted fitness tracking platform".to_string(),
            icon: "/assets/img/app-icons/endurain.png".to_string(),
            repo: "https://github.com/joaovitoriasilva/endurain".to_string(),
        },
        "fedimint" => AppMetadata {
            title: "Fedimint".to_string(),
            description: "Federated Bitcoin mint".to_string(),
            icon: "/assets/img/app-icons/fedimint.png".to_string(),
            repo: "https://github.com/fedimint/fedimint".to_string(),
        },
        "morphos" | "morphos-server" => AppMetadata {
            title: "Morphos".to_string(),
            description: "Self-hosted file converter".to_string(),
            icon: "/assets/img/app-icons/morphos.png".to_string(),
            repo: "https://github.com/danvergara/morphos".to_string(),
        },
        "lnd" | "lightning-stack" => AppMetadata {
            title: "LND".to_string(),
            description: "Lightning Network Daemon".to_string(),
            icon: "/assets/img/app-icons/lnd.svg".to_string(),
            repo: "https://github.com/lightningnetwork/lnd".to_string(),
        },
        "mempool" | "mempool-web" => AppMetadata {
            title: "Mempool".to_string(),
            description: "Bitcoin blockchain explorer".to_string(),
            icon: "/assets/img/app-icons/mempool.webp".to_string(),
            repo: "https://github.com/mempool/mempool".to_string(),
        },
        "mempool-electrs" | "electrs" => AppMetadata {
            title: "Electrs".to_string(),
            description: "Electrum protocol indexer for Bitcoin. Powers Mempool and other Electrum clients.".to_string(),
            icon: "/assets/img/app-icons/electrs.svg".to_string(),
            repo: "https://github.com/romanz/electrs".to_string(),
        },
        "ollama" => AppMetadata {
            title: "Ollama".to_string(),
            description: "Run large language models locally".to_string(),
            icon: "/assets/img/app-icons/ollama.png".to_string(),
            repo: "https://github.com/ollama/ollama".to_string(),
        },
        "searxng" => AppMetadata {
            title: "SearXNG".to_string(),
            description: "Privacy-respecting metasearch engine".to_string(),
            icon: "/assets/img/app-icons/searxng.png".to_string(),
            repo: "https://github.com/searxng/searxng".to_string(),
        },
        "onlyoffice" | "onlyoffice-documentserver" => AppMetadata {
            title: "OnlyOffice".to_string(),
            description: "Office suite and document collaboration".to_string(),
            icon: "/assets/img/app-icons/onlyoffice.webp".to_string(),
            repo: "https://github.com/ONLYOFFICE/DocumentServer".to_string(),
        },
        "penpot" | "penpot-frontend" => AppMetadata {
            title: "Penpot".to_string(),
            description: "Open-source design and prototyping".to_string(),
            icon: "/assets/img/app-icons/penpot.webp".to_string(),
            repo: "https://github.com/penpot/penpot".to_string(),
        },
        "nextcloud" => AppMetadata {
            title: "Nextcloud".to_string(),
            description: "Self-hosted cloud storage and file management".to_string(),
            icon: "/assets/img/app-icons/nextcloud.webp".to_string(),
            repo: "https://github.com/nextcloud/server".to_string(),
        },
        "vaultwarden" => AppMetadata {
            title: "Vaultwarden".to_string(),
            description: "Self-hosted password manager (Bitwarden compatible)".to_string(),
            icon: "/assets/img/app-icons/vaultwarden.webp".to_string(),
            repo: "https://github.com/dani-garcia/vaultwarden".to_string(),
        },
        "jellyfin" => AppMetadata {
            title: "Jellyfin".to_string(),
            description: "Free media server system".to_string(),
            icon: "/assets/img/app-icons/jellyfin.webp".to_string(),
            repo: "https://github.com/jellyfin/jellyfin".to_string(),
        },
        "photoprism" => AppMetadata {
            title: "PhotoPrism".to_string(),
            description: "AI-powered photo management".to_string(),
            icon: "/assets/img/app-icons/photoprims.svg".to_string(),
            repo: "https://github.com/photoprism/photoprism".to_string(),
        },
        "immich" | "immich_server" => AppMetadata {
            title: "Immich".to_string(),
            description: "High-performance self-hosted photo and video backup".to_string(),
            icon: "/assets/img/app-icons/immich.png".to_string(),
            repo: "https://github.com/immich-app/immich".to_string(),
        },
        "filebrowser" => AppMetadata {
            title: "File Browser".to_string(),
            description: "Web-based file manager".to_string(),
            icon: "/assets/img/app-icons/file-browser.webp".to_string(),
            repo: "https://github.com/filebrowser/filebrowser".to_string(),
        },
        "nginx-proxy-manager" => AppMetadata {
            title: "Nginx Proxy Manager".to_string(),
            description: "Easy proxy management with SSL".to_string(),
            icon: "/assets/img/app-icons/nginx.svg".to_string(),
            repo: "https://github.com/NginxProxyManager/nginx-proxy-manager".to_string(),
        },
        "portainer" => AppMetadata {
            title: "Portainer".to_string(),
            description: "Container management UI".to_string(),
            icon: "/assets/img/app-icons/portainer.webp".to_string(),
            repo: "https://github.com/portainer/portainer".to_string(),
        },
        "uptime-kuma" => AppMetadata {
            title: "Uptime Kuma".to_string(),
            description: "Self-hosted monitoring tool".to_string(),
            icon: "/assets/img/app-icons/uptime-kuma.webp".to_string(),
            repo: "https://github.com/louislam/uptime-kuma".to_string(),
        },
        "tailscale" => AppMetadata {
            title: "Tailscale".to_string(),
            description: "Zero-config VPN for secure remote access".to_string(),
            icon: "/assets/img/app-icons/tailscale.webp".to_string(),
            repo: "https://github.com/tailscale/tailscale".to_string(),
        },
        "indeedhub" => AppMetadata {
            title: "Indeehub".to_string(),
            description: "Decentralized media streaming platform".to_string(),
            icon: "/assets/img/app-icons/indeedhub.png".to_string(),
            repo: "https://github.com/indeedhub/indeedhub".to_string(),
        },
        "tor" | "archy-tor" => AppMetadata {
            title: "Tor".to_string(),
            description: "Anonymous overlay network for privacy".to_string(),
            icon: "/assets/img/app-icons/tor.svg".to_string(),
            repo: "https://gitlab.torproject.org/tpo/core/tor".to_string(),
        },
        _ => AppMetadata {
            title: app_id.to_string(),
            description: format!("{} application", app_id),
            icon: "/assets/img/favico.png".to_string(),
            repo: "#".to_string(),
        },
    }
}

/// Map app_id to Tor hidden service directory name.
/// "archipelago" is the main web UI (nginx port 80).
/// Supports container names from deploy (archy-*, btcpay-server, etc.).
fn tor_service_name(app_id: &str) -> Option<&'static str> {
    match app_id {
        "archipelago" => Some("archipelago"),
        "lnd" | "lnd-ui" => Some("lnd"),
        "btcpay" | "btcpay-server" | "btcpayserver" => Some("btcpay"),
        "mempool" | "mempool-web" | "mempool-frontend" => Some("mempool"),
        "fedimint" => Some("fedimint"),
        _ => None,
    }
}

/// V3 onion addresses are 56 base32 chars + ".onion". Placeholders like "btcpay.onion" are not real.
fn is_real_onion_address(s: &str) -> bool {
    s.ends_with(".onion") && s.len() >= 60 && s.len() <= 70
}

/// Read real .onion address from Tor hidden service hostname file.
/// Service name "archipelago" is for the main web UI (nginx port 80).
/// Uses TOR_DATA_DIR env var if set, else /var/lib/archipelago/tor.
pub fn read_tor_address(app_id: &str) -> Option<String> {
    let service = tor_service_name(app_id)?;
    let base = std::env::var("TOR_DATA_DIR").unwrap_or_else(|_| "/var/lib/archipelago/tor".to_string());
    let path = std::path::Path::new(&base)
        .join(format!("hidden_service_{}", service))
        .join("hostname");
    std::fs::read_to_string(&path)
        .ok()
        .map(|s| s.trim().to_string())
        .filter(|s| s.ends_with(".onion") && !s.is_empty())
}

fn extract_lan_address(ports: &[String]) -> Option<String> {
    for port_str in ports {
        // Parse port strings like "0.0.0.0:18443->18443/tcp" or "0.0.0.0:18443-18444->18443-18444/tcp"
        if let Some(public_part) = port_str.split("->").next() {
            if let Some(port_part) = public_part.split(':').nth(1) {
                // Extract just the first port if it's a range (e.g., "18443-18444" -> "18443")
                let single_port = port_part.split('-').next().unwrap_or(port_part);
                return Some(format!("http://localhost:{}", single_port));
            }
        }
    }
    None
}

fn convert_state(container_state: &ContainerState) -> (PackageState, ServiceStatus) {
    match container_state {
        ContainerState::Running => (PackageState::Running, ServiceStatus::Running),
        ContainerState::Stopped | ContainerState::Exited => {
            (PackageState::Stopped, ServiceStatus::Stopped)
        }
        ContainerState::Created => (PackageState::Starting, ServiceStatus::Starting),
        ContainerState::Paused => (PackageState::Stopped, ServiceStatus::Stopped),
        ContainerState::Unknown(_) => (PackageState::Stopped, ServiceStatus::Stopped),
    }
}

fn package_state_str(state: &PackageState) -> &str {
    match state {
        PackageState::Installing => "installing",
        PackageState::Installed => "installed",
        PackageState::Stopping => "stopping",
        PackageState::Stopped => "stopped",
        PackageState::Starting => "starting",
        PackageState::Running => "running",
        PackageState::Restarting => "restarting",
        PackageState::CreatingBackup => "creating-backup",
        PackageState::RestoringBackup => "restoring-backup",
        PackageState::Removing => "removing",
        PackageState::BackingUp => "backing-up",
    }
}
