// vapora-backend: REST API server for VAPORA v1.0
// Phase 1: Complete backend with SurrealDB integration

mod api;
mod audit;
mod config;
mod security;
mod services;
mod workflow;

use std::net::SocketAddr;
use std::sync::Arc;

use anyhow::Result;
use axum::{
    routing::{delete, get, post, put},
    Extension, Router,
};
use clap::Parser;
use tower_http::cors::{Any, CorsLayer};
use tracing::{info, warn, Level};
use vapora_channels::{ChannelConfig, ChannelRegistry};
use vapora_swarm::{SwarmCoordinator, SwarmMetrics};
use vapora_workflow_engine::{ScheduleStore, WorkflowOrchestrator};

use crate::api::websocket::WorkflowBroadcaster;
use crate::api::AppState;
use crate::audit::AuditTrail;
use crate::config::Config;
use crate::services::{
    AgentService, ProjectService, ProposalService, ProviderAnalyticsService, TaskService,
    WorkflowService,
};
use crate::workflow::{executor::StepExecutor, WorkflowEngine};

#[derive(Parser, Debug)]
#[command(
    name = "vapora-backend",
    version,
    about = "VAPORA Backend - REST API server for multi-agent orchestration",
    long_about = "REST API server providing endpoints for project management, task coordination, \
                  and agent orchestration.\n\nConfiguration can be provided via:\n  - CLI \
                  arguments (highest priority)\n  - Environment variables (via config \
                  interpolation)\n  - Config file (default: config/vapora.toml)"
)]
struct Args {
    /// Path to configuration file
    #[arg(
        short,
        long,
        default_value = "config/config.ncl",
        env = "VAPORA_CONFIG"
    )]
    config: String,

    /// Print all #[onto_api] registered routes as JSON and exit.
    /// Pipe to api-catalog.json so the ontoref UI can display this project's
    /// API surface: `just export-api-catalog`
    #[arg(long)]
    dump_api_catalog: bool,
}

#[tokio::main]
async fn main() -> Result<()> {
    // Parse CLI arguments
    let args = Args::parse();

    if args.dump_api_catalog {
        println!("{}", ontoref_ontology::api::dump_catalog_json());
        return Ok(());
    }

    // Load environment variables from .env file if present
    dotenv::dotenv().ok();

    // Initialize logging
    tracing_subscriber::fmt()
        .with_max_level(Level::INFO)
        .with_target(false)
        .compact()
        .init();

    info!("VAPORA Backend v{}", env!("CARGO_PKG_VERSION"));
    info!("Phase 1: Backend Core + SurrealDB");

    // Load configuration from specified path
    let config = Config::load(&args.config)?;
    info!("Configuration loaded successfully");

    // Connect to SurrealDB via WebSocket
    info!("Connecting to SurrealDB at {}", config.database.url);
    let db =
        surrealdb::Surreal::new::<surrealdb::engine::remote::ws::Ws>(&config.database.url).await?;

    // Sign in to database
    db.signin(surrealdb::opt::auth::Root {
        username: "root".to_string(),
        password: "root".to_string(),
    })
    .await?;

    // Use namespace and database
    db.use_ns("vapora").use_db("main").await?;
    info!("Connected to SurrealDB");

    // Initialize services
    let project_service = ProjectService::new(db.clone());
    let task_service = TaskService::new(db.clone());
    let agent_service = AgentService::new(db.clone());
    let proposal_service = ProposalService::new(db.clone());
    let provider_analytics_service = ProviderAnalyticsService::new(db.clone());

    // Create KG Persistence for analytics
    let kg_persistence = Arc::new(vapora_knowledge_graph::KGPersistence::new(db.clone()));

    // Create RLM engine for distributed reasoning (Phase 8)
    let rlm_storage = vapora_rlm::storage::SurrealDBStorage::new(db.clone());
    let rlm_bm25_index = Arc::new(vapora_rlm::search::bm25::BM25Index::new()?);
    let rlm_engine = Arc::new(vapora_rlm::RLMEngine::new(
        Arc::new(rlm_storage),
        rlm_bm25_index,
    )?);
    info!("RLM engine initialized for Phase 8");

    // Initialize schedule store (backed by the same SurrealDB connection)
    let schedule_store = Arc::new(ScheduleStore::new(Arc::new(db.clone())));
    info!("ScheduleStore initialized for autonomous scheduling");

    // Filter out channels whose URLs fail SSRF validation before building the
    // registry.  A misconfigured webhook pointing at an internal address must
    // never receive outbound HTTP traffic, so we remove it here rather than
    // warn-and-proceed.  Channels whose credential fields still contain
    // unresolved `${VAR}` references are passed through — the resolved value
    // is validated at HTTP send time by the channel implementation.
    let safe_channels: std::collections::HashMap<String, ChannelConfig> = config
        .channels
        .into_iter()
        .filter(|(name, channel_cfg)| {
            let raw_url: Option<&str> = match channel_cfg {
                ChannelConfig::Slack(c) if !c.webhook_url.contains("${") => Some(&c.webhook_url),
                ChannelConfig::Discord(c) if !c.webhook_url.contains("${") => Some(&c.webhook_url),
                ChannelConfig::Telegram(c) => {
                    c.api_base.as_deref().filter(|url| !url.contains("${"))
                }
                _ => None,
            };
            match raw_url {
                Some(url) => match security::ssrf::validate_url(url) {
                    Ok(_) => true,
                    Err(e) => {
                        tracing::error!(
                            channel = %name,
                            url = %url,
                            reason = %e,
                            "Channel blocked: URL failed SSRF validation"
                        );
                        false
                    }
                },
                None => true,
            }
        })
        .collect();

    // Build notification channel registry from [channels] config block.
    // Absent block → no notifications sent; a build error is non-fatal (warns).
    let channel_registry = if safe_channels.is_empty() {
        None
    } else {
        match ChannelRegistry::from_map(safe_channels) {
            Ok(r) => {
                info!(
                    "Channel registry built ({} channels)",
                    r.channel_names().len()
                );
                Some(std::sync::Arc::new(r))
            }
            Err(e) => {
                tracing::warn!("Failed to build channel registry: {e}; notifications disabled");
                None
            }
        }
    };

    // Create application state
    let mut app_state = AppState::new(
        project_service,
        task_service,
        agent_service,
        proposal_service,
        provider_analytics_service,
    )
    .with_rlm_engine(rlm_engine)
    .with_schedule_store(schedule_store)
    .with_notification_config(config.notifications.clone());
    if let Some(registry) = channel_registry {
        app_state = app_state.with_channel_registry(registry);
    }

    // Create SwarmMetrics for Prometheus monitoring
    let metrics = match SwarmMetrics::new() {
        Ok(m) => {
            info!("SwarmMetrics initialized for Prometheus monitoring");
            m
        }
        Err(e) => {
            tracing::warn!(
                "Failed to initialize SwarmMetrics: {:?}, continuing without metrics",
                e
            );
            // Create new registry and metrics as fallback
            SwarmMetrics::new().unwrap()
        }
    };

    // Create SwarmCoordinator for multi-agent coordination
    let mut swarm_coordinator = SwarmCoordinator::new();
    swarm_coordinator.set_metrics(Arc::clone(&metrics));
    let swarm_coordinator = Arc::new(swarm_coordinator);
    info!("SwarmCoordinator initialized for Phase 5.2");

    // Connect to NATS and initialize WorkflowOrchestrator for schedule firing.
    // Both steps are non-fatal: if NATS is unavailable or the workflow config
    // file is missing, the orchestrator stays None and /schedules/*/fire returns
    // 503 instead of panicking.
    match async_nats::connect(&config.nats.url).await {
        Ok(nats_client) => {
            info!("Connected to NATS at {}", config.nats.url);
            let nats = Arc::new(nats_client);
            match WorkflowOrchestrator::new(
                "config/workflows.toml",
                swarm_coordinator.clone(),
                kg_persistence.clone(),
                nats,
                db.clone(),
            )
            .await
            {
                Ok(orchestrator) => {
                    app_state = app_state.with_workflow_orchestrator(Arc::new(orchestrator));
                    info!("WorkflowOrchestrator initialized; /schedules/*/fire operational");
                }
                Err(e) => {
                    warn!(
                        "WorkflowOrchestrator init failed: {e}; /schedules/*/fire will return 503"
                    );
                }
            }
        }
        Err(e) => {
            warn!(
                "NATS connect failed ({}): {e}; workflow orchestrator disabled",
                config.nats.url
            );
        }
    }

    // Initialize WorkflowService (backend workflow management: CRUD, execute, audit
    // trail) Non-fatal: if agent coordinator init fails, /api/v1/workflows/*
    // returns 503.
    {
        let registry = Arc::new(vapora_agents::AgentRegistry::new(10));
        let agent_config = vapora_agents::AgentConfig::default();
        match vapora_agents::coordinator::AgentCoordinator::new(agent_config, registry).await {
            Ok(coordinator) => {
                let executor = StepExecutor::new(Arc::new(coordinator));
                let engine = Arc::new(WorkflowEngine::new(executor));
                let broadcaster = Arc::new(WorkflowBroadcaster::new());
                let audit = Arc::new(AuditTrail::new(db.clone()));
                let svc = Arc::new(WorkflowService::new(engine, broadcaster, audit));
                app_state = app_state.with_workflow_service(svc);
                info!("WorkflowService initialized; /api/v1/workflows/* operational");
            }
            Err(e) => {
                warn!("WorkflowService init failed: {e}; /api/v1/workflows/* will return 503");
            }
        }
    }

    // Initialize analytics metrics (Phase 6)
    api::analytics_metrics::register_analytics_metrics();
    info!("Analytics metrics registered for Prometheus");

    // Initialize provider metrics (Phase 7)
    api::provider_metrics::register_provider_metrics();
    info!("Provider metrics registered for Prometheus");

    // Start metrics collector background task (Phase 6)
    let metrics_collector =
        api::metrics_collector::MetricsCollector::new(kg_persistence.clone(), 60);
    let _collector_handle = metrics_collector.start();
    info!("Metrics collector started (60s interval)");

    // Configure CORS
    let cors = CorsLayer::new()
        .allow_origin(Any)
        .allow_methods(Any)
        .allow_headers(Any);

    // Build router
    let app = Router::new()
        // Health endpoint
        .route("/health", get(api::health::health))
        // Metrics endpoint (Prometheus)
        .route("/metrics", get(api::metrics::metrics_handler))
        // Project endpoints
        .route(
            "/api/v1/projects",
            get(api::projects::list_projects).post(api::projects::create_project),
        )
        .route(
            "/api/v1/projects/:id",
            get(api::projects::get_project)
                .put(api::projects::update_project)
                .delete(api::projects::delete_project),
        )
        .route(
            "/api/v1/projects/:id/features",
            post(api::projects::add_feature),
        )
        .route(
            "/api/v1/projects/:id/features/:feature",
            delete(api::projects::remove_feature),
        )
        .route(
            "/api/v1/projects/:id/archive",
            post(api::projects::archive_project),
        )
        // Task endpoints
        .route(
            "/api/v1/tasks",
            get(api::tasks::list_tasks).post(api::tasks::create_task),
        )
        .route(
            "/api/v1/tasks/:id",
            get(api::tasks::get_task)
                .put(api::tasks::update_task)
                .delete(api::tasks::delete_task),
        )
        .route("/api/v1/tasks/:id/reorder", put(api::tasks::reorder_task))
        .route(
            "/api/v1/tasks/:id/status",
            put(api::tasks::update_task_status),
        )
        .route("/api/v1/tasks/:id/assign", put(api::tasks::assign_task))
        .route(
            "/api/v1/tasks/:id/priority",
            put(api::tasks::update_priority),
        )
        // Agent endpoints (specific routes before parameterized routes)
        .route(
            "/api/v1/agents",
            get(api::agents::list_agents).post(api::agents::register_agent),
        )
        .route(
            "/api/v1/agents/available",
            get(api::agents::get_available_agents),
        )
        .route(
            "/api/v1/agents/:id",
            get(api::agents::get_agent)
                .put(api::agents::update_agent)
                .delete(api::agents::deregister_agent),
        )
        .route(
            "/api/v1/agents/:id/health",
            get(api::agents::check_agent_health),
        )
        .route(
            "/api/v1/agents/:id/status",
            put(api::agents::update_agent_status),
        )
        .route(
            "/api/v1/agents/:id/capabilities",
            post(api::agents::add_capability),
        )
        .route(
            "/api/v1/agents/:id/capabilities/:capability",
            delete(api::agents::remove_capability),
        )
        .route("/api/v1/agents/:id/skills", post(api::agents::add_skill))
        // Proposal endpoints (Approval Gates)
        .route(
            "/api/v1/proposals",
            get(api::proposals::list_proposals).post(api::proposals::create_proposal),
        )
        .route(
            "/api/v1/proposals/:id",
            get(api::proposals::get_proposal)
                .put(api::proposals::update_proposal)
                .delete(api::proposals::delete_proposal),
        )
        .route(
            "/api/v1/proposals/:id/submit",
            put(api::proposals::submit_proposal),
        )
        .route(
            "/api/v1/proposals/:id/approve",
            put(api::proposals::approve_proposal),
        )
        .route(
            "/api/v1/proposals/:id/reject",
            put(api::proposals::reject_proposal),
        )
        .route(
            "/api/v1/proposals/:id/executed",
            put(api::proposals::mark_executed),
        )
        .route(
            "/api/v1/proposals/:id/reviews",
            get(api::proposals::list_reviews).post(api::proposals::add_review),
        )
        // Tracking endpoints
        .route(
            "/api/v1/tracking/entries",
            get(api::tracking::list_tracking_entries),
        )
        .route(
            "/api/v1/tracking/summary",
            get(api::tracking::get_tracking_summary),
        )
        .route(
            "/api/v1/tracking/health",
            get(api::tracking::tracking_health),
        )
        // Swarm endpoints (Phase 5.2)
        .route("/api/v1/swarm/stats", get(api::swarm::swarm_statistics))
        .route("/api/v1/swarm/health", get(api::swarm::swarm_health))
        // Analytics endpoints (Phase 6)
        .route(
            "/api/v1/analytics/agent/:id",
            get(api::analytics::get_agent_performance),
        )
        .route(
            "/api/v1/analytics/task-types/:task_type",
            get(api::analytics::get_task_type_analytics),
        )
        .route(
            "/api/v1/analytics/dashboard",
            get(api::analytics::get_dashboard_metrics),
        )
        .route(
            "/api/v1/analytics/cost-report",
            get(api::analytics::get_cost_report),
        )
        .route(
            "/api/v1/analytics/summary",
            get(api::analytics::get_analytics_summary),
        )
        // Provider analytics endpoints (Phase 7)
        .route(
            "/api/v1/analytics/providers",
            get(api::provider_analytics::get_provider_cost_breakdown),
        )
        .route(
            "/api/v1/analytics/providers/efficiency",
            get(api::provider_analytics::get_provider_efficiency),
        )
        .route(
            "/api/v1/analytics/providers/:provider",
            get(api::provider_analytics::get_provider_analytics),
        )
        .route(
            "/api/v1/analytics/providers/:provider/forecast",
            get(api::provider_analytics::get_provider_forecast),
        )
        .route(
            "/api/v1/analytics/providers/:provider/tasks/:task_type",
            get(api::provider_analytics::get_provider_task_type_metrics),
        )
        // Channel endpoints
        .route("/api/v1/channels", get(api::channels::list_channels))
        .route(
            "/api/v1/channels/:name/test",
            post(api::channels::test_channel),
        )
        // RLM endpoints (Phase 8)
        .route("/api/v1/rlm/documents", post(api::rlm::load_document))
        .route("/api/v1/rlm/query", post(api::rlm::query_document))
        .route("/api/v1/rlm/analyze", post(api::rlm::analyze_document))
        // Schedule endpoints
        .route("/api/v1/schedules", get(api::schedules::list_schedules))
        .route(
            "/api/v1/schedules/:id",
            get(api::schedules::get_schedule)
                .put(api::schedules::put_schedule)
                .patch(api::schedules::patch_schedule)
                .delete(api::schedules::delete_schedule),
        )
        .route(
            "/api/v1/schedules/:id/runs",
            get(api::schedules::list_schedule_runs),
        )
        .route(
            "/api/v1/schedules/:id/fire",
            post(api::schedules::fire_schedule),
        )
        // Workflow management endpoints
        .nest("/api/v1/workflows", api::workflows::workflow_routes())
        // API catalog endpoint
        .route("/api/v1/catalog", get(api::catalog::api_catalog))
        // Apply CORS, state, and extensions
        .layer(Extension(swarm_coordinator))
        .layer(cors)
        .with_state(app_state);

    // Start server
    let addr = SocketAddr::from(([127, 0, 0, 1], config.server.port));
    info!("Server listening on {}", addr);
    info!("Health check: http://{}/health", addr);
    info!("API documentation: http://{}/api/v1", addr);

    let listener = tokio::net::TcpListener::bind(addr).await?;
    axum::serve(listener, app).await?;

    Ok(())
}
