//! Fleet orchestrator
//!
//! Coordinates ships as equal peers with dependency-based ordering.

use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;

use tokio::sync::{Mutex, Notify};
use tokio::time::sleep;
use tracing::{debug, error, info, warn};

use super::health::HealthChecker;
use super::ship::{Ship, ShipBuilder, ShipSnapshot};
use crate::config::Manifest;

/// Fleet orchestrator
pub struct Fleet {
    /// All ships (by name)
    pub ships: HashMap<String, Arc<Ship>>,
    /// Group membership (group name -> ship names)
    pub groups: HashMap<String, Vec<String>>,
    /// Health checker
    health_checker: HealthChecker,
    /// Shutdown flag
    shutdown: Arc<Mutex<bool>>,
}

impl Fleet {
    /// Create fleet from manifest
    pub fn from_manifest(manifest: &Manifest) -> Self {
        let mut ships = HashMap::new();
        let mut groups = HashMap::new();

        // Flatten all fleet groups into ships
        for (group_name, group_ships) in &manifest.fleet {
            let mut group_ship_names = Vec::new();

            for ship_config in group_ships {
                let ship = Arc::new(
                    ShipBuilder::new(&ship_config.name, &ship_config.command)
                        .group(group_name)
                        .args(ship_config.args.clone())
                        .env(ship_config.env.clone())
                        .bind(ship_config.bind.clone())
                        .healthcheck(ship_config.healthcheck.clone())
                        .depends_on(ship_config.depends_on.clone())
                        .routes(ship_config.routes.clone())
                        .critical(ship_config.critical)
                        .oneshot(ship_config.oneshot)
                        .build(),
                );
                group_ship_names.push(ship_config.name.clone());
                ships.insert(ship_config.name.clone(), ship);
            }

            groups.insert(group_name.clone(), group_ship_names);
        }

        Self {
            ships,
            groups,
            health_checker: HealthChecker::new(),
            shutdown: Arc::new(Mutex::new(false)),
        }
    }

    /// Launch the entire fleet
    pub async fn launch(&self) -> anyhow::Result<()> {
        info!("Launching fleet ({} ships)", self.ships.len());

        if self.ships.is_empty() {
            info!("No ships to launch");
            return Ok(());
        }

        // Launch ships in dependency order
        let launch_order = self.resolve_launch_order();
        for name in launch_order {
            if let Some(ship) = self.ships.get(&name) {
                // Wait for dependencies first
                for dep in &ship.depends_on {
                    if let Some(dep_ship) = self.ships.get(dep) {
                        // Wait for dependency to be healthy (or completed if oneshot)
                        self.wait_for_ready(dep_ship).await?;
                    } else {
                        warn!(
                            ship = %name, dependency = %dep,
                            "Dependency not found, skipping"
                        );
                    }
                }

                // Launch this ship
                ship.launch().await?;

                // Handle oneshot vs long-running
                if ship.oneshot {
                    // Wait for oneshot to complete
                    let code = ship.wait().await?;
                    if code != 0 {
                        if ship.critical {
                            return Err(anyhow::anyhow!(
                                "Oneshot job '{}' failed with exit code {}",
                                name,
                                code
                            ));
                        }
                        warn!(
                            ship = %name,
                            exit_code = code,
                            "Non-critical oneshot failed"
                        );
                    } else {
                        info!(ship = %name, "Oneshot job completed");
                    }
                } else {
                    // Wait for health check or mark healthy
                    self.wait_for_ready(ship).await?;
                    info!(ship = %name, group = %ship.group, "Ship launched");
                }
            }
        }

        info!("Fleet launched successfully");
        Ok(())
    }

    /// Resolve launch order based on dependencies (topological sort)
    fn resolve_launch_order(&self) -> Vec<String> {
        let mut order = Vec::new();
        let mut remaining: Vec<_> = self.ships.keys().cloned().collect();

        while !remaining.is_empty() {
            let mut progress = false;
            remaining.retain(|name| {
                if let Some(ship) = self.ships.get(name) {
                    let deps_satisfied = ship
                        .depends_on
                        .iter()
                        .all(|dep| order.contains(dep) || !self.ships.contains_key(dep));
                    if deps_satisfied {
                        order.push(name.clone());
                        progress = true;
                        return false;
                    }
                }
                true
            });

            if !progress && !remaining.is_empty() {
                warn!(
                    "Possible circular dependency in fleet: {:?}",
                    remaining
                );
                order.append(&mut remaining);
            }
        }

        order
    }

    /// Wait for a ship to become ready (healthy or completed)
    async fn wait_for_ready(&self, ship: &Ship) -> anyhow::Result<()> {
        let name = ship.display_name();

        // Oneshot jobs - wait for completion
        if ship.oneshot {
            // Already handled in launch()
            return Ok(());
        }

        // Check for healthcheck URL
        let url = match ship.healthcheck_url() {
            Some(url) => url,
            None => {
                // No healthcheck - mark healthy after short delay
                sleep(Duration::from_millis(500)).await;
                ship.mark_healthy().await;
                ship.reset_breaker().await;
                return Ok(());
            }
        };

        info!(ship = name, url = %url, "Waiting for health check");

        let max_attempts = 30; // 30 * 2s = 60s max wait
        for attempt in 1..=max_attempts {
            if !ship.is_running().await {
                return Err(anyhow::anyhow!("Ship {} died during startup", name));
            }

            if self.health_checker.check_http(&url).await {
                ship.mark_healthy().await;
                ship.reset_breaker().await;
                return Ok(());
            }

            if attempt < max_attempts {
                sleep(Duration::from_secs(2)).await;
            }
        }

        Err(anyhow::anyhow!(
            "Ship {} failed health check after {} attempts",
            name,
            max_attempts
        ))
    }

    /// Monitor fleet health and restart crashed ships
    pub async fn monitor(&self, shutdown_notify: Arc<Notify>) {
        let interval = self.health_checker.interval();

        loop {
            if *self.shutdown.lock().await {
                break;
            }

            for (name, ship) in &self.ships {
                // Skip oneshot jobs
                if ship.oneshot {
                    continue;
                }

                if ship.is_backing_off().await {
                    if !ship.backoff_expired().await {
                        continue;
                    }
                    ship.clear_backoff().await;
                    ship.increment_restart().await;
                    if let Err(e) = ship.launch().await {
                        error!(ship = %name, error = %e, "Failed to restart ship");
                        let breaker_open = ship.record_crash().await;
                        if breaker_open {
                            ship.enter_backoff().await;
                        }
                        if ship.critical {
                            error!("Critical ship failed to restart, shutting down fleet");
                            *self.shutdown.lock().await = true;
                            shutdown_notify.notify_one();
                            return;
                        }
                    } else if ship.healthcheck_url().is_none() {
                        ship.mark_healthy().await;
                        ship.reset_breaker().await;
                    }
                    continue;
                }

                if !ship.is_running().await {
                    ship.mark_failed().await;
                    let breaker_open = ship.record_crash().await;
                    if breaker_open {
                        warn!(
                            ship = %name,
                            critical = ship.critical,
                            "Restart circuit open, entering backoff"
                        );
                        ship.enter_backoff().await;
                        if ship.critical {
                            error!("Critical ship entered backoff, shutting down fleet");
                            *self.shutdown.lock().await = true;
                            shutdown_notify.notify_one();
                            return;
                        }
                        continue;
                    }

                    ship.increment_restart().await;
                    if let Err(e) = ship.launch().await {
                        error!(ship = %name, error = %e, "Failed to restart ship");
                        let breaker_open = ship.record_crash().await;
                        if breaker_open {
                            ship.enter_backoff().await;
                        }
                        if ship.critical {
                            error!("Critical ship failed to restart, shutting down fleet");
                            *self.shutdown.lock().await = true;
                            shutdown_notify.notify_one();
                            return;
                        }
                    } else if ship.healthcheck_url().is_none() {
                        ship.mark_healthy().await;
                        ship.reset_breaker().await;
                    }
                } else if let Some(url) = ship.healthcheck_url() {
                    // Skip health-triggered restarts during grace period
                    let in_grace = ship.in_grace_period().await;
                    let healthy = self.health_checker.check_http(&url).await;

                    if healthy {
                        ship.record_health_success().await;
                        ship.mark_healthy().await;
                    } else if in_grace {
                        // During grace period, mark unhealthy but don't trigger restart
                        debug!(
                            ship = %name,
                            "Health check failed during grace period, waiting"
                        );
                        ship.mark_unhealthy().await;
                    } else {
                        ship.mark_unhealthy().await;
                        let breaker_open = ship.record_health_failure().await;
                        if breaker_open {
                            warn!(
                                ship = %name,
                                critical = ship.critical,
                                "Health check failures opened restart circuit"
                            );
                            let _ = ship.terminate().await;
                            ship.enter_backoff().await;
                            if ship.critical {
                                error!("Critical ship entered backoff, shutting down fleet");
                                *self.shutdown.lock().await = true;
                                shutdown_notify.notify_one();
                                return;
                            }
                        }
                    }
                }
            }

            sleep(interval).await;
        }
    }

    /// Graceful shutdown of the fleet
    pub async fn shutdown(&self) {
        info!("Initiating fleet shutdown");
        *self.shutdown.lock().await = true;

        // Shutdown in reverse dependency order
        let shutdown_order: Vec<_> = self.resolve_launch_order().into_iter().rev().collect();

        for name in &shutdown_order {
            if let Some(ship) = self.ships.get(name) {
                // Skip oneshot (already completed)
                if ship.oneshot {
                    continue;
                }

                info!(ship = %name, "Shutting down ship");
                let _ = ship.terminate().await;
            }
        }

        info!("Fleet shutdown complete");
    }

    /// Get all ships for iteration
    pub fn all_ships(&self) -> Vec<Arc<Ship>> {
        self.ships.values().cloned().collect()
    }

    /// Get snapshots of all ships (for sync access like TUI)
    pub async fn ships_snapshot(&self) -> Vec<ShipSnapshot> {
        let mut snapshots = Vec::new();
        for ship in self.ships.values() {
            snapshots.push(ship.snapshot().await);
        }
        // Sort by group then name for consistent display
        snapshots.sort_by(|a, b| {
            a.group()
                .cmp(b.group())
                .then_with(|| a.name().cmp(b.name()))
        });
        snapshots
    }

    /// Get logs for a ship by index
    pub async fn ship_logs(&self, index: usize, limit: usize) -> Vec<super::ship::LogEntry> {
        let snapshots = self.ships_snapshot().await;
        if index < snapshots.len() {
            let name = snapshots[index].name();
            if let Some(ship) = self.ships.get(name) {
                return ship.logs(limit).await;
            }
        }
        vec![]
    }

    /// Get ship count
    pub fn ship_count(&self) -> usize {
        self.ships.len()
    }
}
