//! Mothership - Process supervisor with HTTP exposure
//!
//! Usage:
//!   mothership                    # Load ship-manifest.toml from CWD
//!   mothership -c /path/to.toml   # Load specific manifest
//!   mothership validate           # Validate manifest
//!   mothership --tui              # Run with TUI dashboard

use anyhow::Result;
use clap::Parser;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::{watch, Notify};
use tracing::info;
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};

use mothership::config::Manifest;
use mothership::fleet::{Fleet, set_suppress_stdout};
use mothership::http::{HttpExposure, MetricsRegistry, MetricsServer};
use mothership::tui::TuiApp;

/// Tag filter for selecting ships
#[derive(Debug, Clone, Default)]
pub struct TagFilter {
    /// Only run ships with these tags (OR logic)
    pub only: Vec<String>,
    /// Exclude ships with these tags
    pub except: Vec<String>,
}

impl TagFilter {
    /// Check if a ship's tags pass the filter
    pub fn matches(&self, tags: &[String]) -> bool {
        // If --except specified, reject ships with any of those tags
        if !self.except.is_empty() {
            for tag in tags {
                if self.except.contains(tag) {
                    return false;
                }
            }
        }

        // If --only specified, require at least one matching tag
        if !self.only.is_empty() {
            for tag in tags {
                if self.only.contains(tag) {
                    return true;
                }
            }
            return false;
        }

        true
    }

    pub fn is_empty(&self) -> bool {
        self.only.is_empty() && self.except.is_empty()
    }
}

/// Merge two vecs, flattening nested values from ArgAction::Append
fn merge_vecs(a: Vec<String>, b: Vec<String>) -> Vec<String> {
    let mut result = a;
    result.extend(b);
    result
}

/// Mothership - Process supervisor with HTTP exposure
#[derive(Parser)]
#[command(name = "mothership")]
#[command(about = "Wrap, monitor, and expose your fleet", long_about = None)]
#[command(disable_version_flag = true)]
struct Cli {
    /// Print version
    #[arg(short = 'v', long = "version")]
    version: bool,

    #[command(subcommand)]
    command: Option<Commands>,

    /// Path to ship-manifest.toml
    #[arg(short, long, value_name = "FILE", default_value = "ship-manifest.toml")]
    config: PathBuf,

    /// Enable TUI dashboard (disables JSON logging to stdout)
    #[arg(long)]
    tui: bool,

    /// Only run ships with these tags (comma-separated or multiple flags)
    #[arg(long, value_delimiter = ',', action = clap::ArgAction::Append)]
    only: Vec<String>,

    /// Exclude ships with these tags (comma-separated or multiple flags)
    #[arg(long, value_delimiter = ',', action = clap::ArgAction::Append)]
    except: Vec<String>,
}

#[derive(clap::Subcommand)]
enum Commands {
    /// Run the fleet (default)
    Run {
        /// Path to ship-manifest.toml
        #[arg(short, long, value_name = "FILE")]
        config: Option<PathBuf>,

        /// Enable TUI dashboard
        #[arg(long)]
        tui: bool,

        /// Only run ships with these tags
        #[arg(long, value_delimiter = ',', action = clap::ArgAction::Append)]
        only: Vec<String>,

        /// Exclude ships with these tags
        #[arg(long, value_delimiter = ',', action = clap::ArgAction::Append)]
        except: Vec<String>,
    },
    /// Request launch clearance (validate manifest)
    #[clap(alias = "validate")]
    Clearance {
        /// Path to ship-manifest.toml
        #[arg(short, long, value_name = "FILE")]
        config: Option<PathBuf>,
    },
    /// Initialize a new ship-manifest.toml
    Init {
        /// Force overwrite if file exists
        #[arg(short, long)]
        force: bool,
    },
}

#[tokio::main]
async fn main() -> Result<()> {
    let cli = Cli::parse();

    // Handle version flag
    if cli.version {
        println!("mothership {}", env!("CARGO_PKG_VERSION"));
        return Ok(());
    }

    // Determine if TUI is enabled
    let tui_enabled = match &cli.command {
        Some(Commands::Run { tui, .. }) => *tui || cli.tui,
        None => cli.tui,
        _ => false,
    };

    // Initialize logging (skip JSON to stdout if TUI is enabled)
    if !tui_enabled {
        tracing_subscriber::registry()
            .with(fmt::layer().json())
            .with(EnvFilter::from_default_env().add_directive("mothership=info".parse()?))
            .init();
    }

    match cli.command {
        Some(Commands::Init { force }) => {
            init_manifest(force)?;
        }
        Some(Commands::Clearance { config }) => {
            let path = config.unwrap_or(cli.config);
            validate_manifest(&path)?;
        }
        Some(Commands::Run { config, tui, only, except }) => {
            let path = config.unwrap_or(cli.config);
            let filter = TagFilter {
                only: merge_vecs(cli.only, only),
                except: merge_vecs(cli.except, except),
            };
            run_fleet(&path, tui || cli.tui, filter).await?;
        }
        None => {
            let filter = TagFilter {
                only: cli.only,
                except: cli.except,
            };
            run_fleet(&cli.config, cli.tui, filter).await?;
        }
    }

    Ok(())
}

fn init_manifest(force: bool) -> Result<()> {
    let path = PathBuf::from("ship-manifest.toml");

    if path.exists() && !force {
        anyhow::bail!(
            "ship-manifest.toml already exists. Use --force to overwrite."
        );
    }

    let template = r#"# Ship Manifest - Fleet Configuration
# https://github.com/seuros/mothership

# =============================================================================
# BASE TEMPLATES (optional - reduce duplication)
# =============================================================================
# Define defaults inherited by all ships/modules

# [base.ship]
# env = { RAILS_ENV = "production" }
# critical = true
# tags = ["ruby"]
# comment = "Default ship settings"

# [base.module]
# phase = "request"
# tags = ["security"]

# =============================================================================
# MOTHERSHIP (global configuration)
# =============================================================================

[mothership]
metrics_port = 9090  # Prometheus metrics at http://127.0.0.1:9090/metrics

# Named binds (external listeners)
[mothership.bind]
http = "0.0.0.0:80"
# https = "0.0.0.0:443"
# ws = "0.0.0.0:8080"

# Environment variables inherited by all ships
# [mothership.env]
# RAILS_ENV = "production"

# =============================================================================
# FLEET (ship definitions)
# =============================================================================
# Ships can be filtered by tags at runtime:
#   mothership --only web          # Only ships tagged "web"
#   mothership --only web,api      # Ships tagged "web" OR "api"
#   mothership --except workers    # All except "workers"

# Web ships (HTTP-serving applications)
[[fleet.web]]
name = "app"
command = "ruby"
args = ["app.rb"]
bind = "tcp://127.0.0.1:3000"
healthcheck = "/health"
tags = ["web", "ruby"]
routes = [
  { bind = "http", pattern = "/.*" },
]

# Background workers
# [[fleet.workers]]
# name = "sidekiq"
# command = "bundle"
# args = ["exec", "sidekiq"]
# critical = false  # Non-critical: crash won't kill fleet
# tags = ["workers", "ruby"]

# One-shot jobs (run once and exit)
# [[fleet.jobs]]
# name = "migrate"
# command = "bundle"
# args = ["exec", "rails", "db:migrate"]
# oneshot = true
# tags = ["jobs", "ruby"]

# WebSocket server
# [[fleet.web]]
# name = "cable"
# command = "anycable-go"
# bind = "tcp://127.0.0.1:8081"
# depends_on = ["app"]
# tags = ["web", "websocket"]
# routes = [
#   { bind = "ws", pattern = "/cable" },
# ]

# =============================================================================
# WASM MODULES (optional plugins)
# =============================================================================

# [[modules]]
# name = "auth"
# wasm = "modules/auth.wasm"
# routes = ["/admin/.*"]
# phase = "request"
# tags = ["security"]
"#;

    std::fs::write(&path, template)?;
    println!("Created ship-manifest.toml");
    println!();
    println!("Next steps:");
    println!("  1. Edit ship-manifest.toml with your app configuration");
    println!("  2. Run: mothership clearance");
    println!("  3. Run: mothership --tui");

    Ok(())
}

fn validate_manifest(path: &PathBuf) -> Result<()> {
    info!(path = %path.display(), "Validating manifest");

    let manifest = Manifest::load(path)?;

    println!("✓ Manifest is valid!");
    println!();

    // Global config
    println!("Mothership (global config):");
    if !manifest.mothership.bind.is_empty() {
        println!("  Binds:");
        for (name, bind) in &manifest.mothership.bind {
            println!("    {}: {:?}", name, bind);
        }
    }
    if let Some(port) = manifest.mothership.metrics_port {
        println!("  Metrics: http://127.0.0.1:{}/metrics", port);
    }

    // Fleet groups
    if !manifest.fleet.is_empty() {
        println!();
        println!("Fleet ({} ships across {} groups):", manifest.ship_count(), manifest.fleet.len());
        for (group_name, ships) in &manifest.fleet {
            println!();
            println!("  [{}] ({} ships):", group_name, ships.len());
            for ship in ships {
                let flags = format!(
                    "{}{}",
                    if ship.critical { "" } else { " [non-critical]" },
                    if ship.oneshot { " [oneshot]" } else { "" }
                );
                println!("    • {}: {}{}", ship.name, ship.command, flags);
                if !ship.tags.is_empty() {
                    println!("      tags: {:?}", ship.tags);
                }
                if !ship.depends_on.is_empty() {
                    println!("      depends_on: {:?}", ship.depends_on);
                }
                if !ship.routes.is_empty() {
                    let routes: Vec<String> = ship.routes.iter().map(|r| r.to_string()).collect();
                    println!("      routes: {:?}", routes);
                }
            }
        }
    } else {
        println!();
        println!("Fleet: (empty - no ships configured)");
    }

    if !manifest.modules.is_empty() {
        println!();
        println!("WASM Modules ({}):", manifest.modules.len());
        for m in &manifest.modules {
            println!("  • {}: {} [phase: {:?}]", m.name, m.wasm, m.phase);
            if !m.tags.is_empty() {
                println!("    tags: {:?}", m.tags);
            }
        }
    }

    Ok(())
}

async fn run_fleet(path: &PathBuf, tui_enabled: bool, filter: TagFilter) -> Result<()> {
    // Suppress stdout logging if TUI is enabled
    if tui_enabled {
        set_suppress_stdout(true);
    } else {
        info!(path = %path.display(), "Loading manifest");
    }

    let mut manifest = Manifest::load(path)?;

    // Apply tag filter to ships
    if !filter.is_empty() {
        let before_count = manifest.ship_count();
        manifest.filter_ships(&|tags| filter.matches(tags));
        let after_count = manifest.ship_count();
        if !tui_enabled {
            info!(
                before = before_count,
                after = after_count,
                only = ?filter.only,
                except = ?filter.except,
                "Applied tag filter"
            );
        }
    }

    let fleet = Arc::new(Fleet::from_manifest(&manifest));

    // Set up shutdown channels
    let shutdown = Arc::new(Notify::new());
    let (http_shutdown_tx, http_shutdown_rx) = watch::channel(false);
    let (metrics_shutdown_tx, metrics_shutdown_rx) = watch::channel(false);
    let (tui_shutdown_tx, tui_shutdown_rx) = watch::channel(false);

    // Create metrics registry (shared with HTTP proxy for request counting)
    let metrics_registry = Arc::new(MetricsRegistry::new());

    // Signal handler
    let shutdown_signal = shutdown.clone();
    let http_shutdown = http_shutdown_tx.clone();
    let metrics_shutdown = metrics_shutdown_tx.clone();
    let tui_shutdown = tui_shutdown_tx.clone();
    tokio::spawn(async move {
        tokio::signal::ctrl_c().await.ok();
        if !tui_enabled {
            info!("Received SIGINT, initiating shutdown");
        }
        shutdown_signal.notify_one();
        let _ = http_shutdown.send(true);
        let _ = metrics_shutdown.send(true);
        let _ = tui_shutdown.send(true);
    });

    // Launch fleet (processes)
    fleet.launch().await?;

    // Start HTTP exposure layer if configured
    let http_handle = if let Some(http_exposure) = HttpExposure::from_manifest(&manifest) {
        Some(tokio::spawn(async move {
            if let Err(e) = http_exposure.run(http_shutdown_rx).await {
                tracing::error!(error = %e, "HTTP exposure layer failed");
            }
        }))
    } else {
        if !tui_enabled {
            info!("No mothership.bind configured, HTTP exposure disabled");
        }
        None
    };

    // Start metrics server if configured
    let metrics_handle = if let Some(port) = manifest.mothership.metrics_port {
        let metrics_fleet = fleet.clone();
        let metrics_reg = metrics_registry.clone();
        Some(tokio::spawn(async move {
            let server = MetricsServer::new(port, metrics_fleet, metrics_reg);
            server.run(metrics_shutdown_rx).await;
        }))
    } else {
        None
    };

    // Monitor fleet in background
    let fleet_monitor = fleet.clone();
    let shutdown_notify = shutdown.clone();
    let monitor_handle = tokio::spawn(async move {
        fleet_monitor.monitor(shutdown_notify).await;
    });

    // Start TUI if enabled
    let tui_handle = if tui_enabled {
        let tui_fleet = fleet.clone();
        Some(tokio::spawn(async move {
            match TuiApp::new(tui_fleet, tui_shutdown_rx).await {
                Ok(mut app) => {
                    if let Err(e) = app.run().await {
                        eprintln!("TUI error: {}", e);
                    }
                }
                Err(e) => {
                    eprintln!("Failed to start TUI: {}", e);
                }
            }
        }))
    } else {
        None
    };

    // Wait for shutdown signal (or TUI exit)
    if let Some(mut tui) = tui_handle {
        // TUI mode - wait for TUI exit or shutdown signal
        tokio::select! {
            _ = &mut tui => {
                shutdown.notify_one();
            }
            _ = shutdown.notified() => {
                let _ = tui_shutdown_tx.send(true);
                let _ = tui.await;
            }
        }
    } else {
        // Headless mode - wait for signal
        shutdown.notified().await;
    }

    // Stop monitoring
    monitor_handle.abort();

    // Stop HTTP server
    if let Some(handle) = http_handle {
        let _ = http_shutdown_tx.send(true);
        let _ = handle.await;
    }

    // Stop metrics server
    if let Some(handle) = metrics_handle {
        let _ = metrics_shutdown_tx.send(true);
        let _ = handle.await;
    }

    // Graceful shutdown of processes
    fleet.shutdown().await;

    Ok(())
}
