use std::sync::Arc;

use axum::Router;
use axum::middleware;
use axum::routing::{delete, get, post, put};
use tower_http::cors::{AllowOrigin, CorsLayer};
use tower_http::trace::TraceLayer;

use super::handlers;
use super::middleware::auth_middleware;
use super::state::AppState;

/// Build the complete axum Router with all routes, middleware, and state.
pub fn build_router(state: Arc<AppState>) -> Router {
    let cors = build_cors(&state.config.load().gateway_cors_origins);

    Router::new()
        // System (no auth for health / agent card)
        .route("/health", get(handlers::health::health))
        .route(
            "/.well-known/agent.json",
            get(handlers::agent_card::agent_card),
        )
        // Sessions
        .route(
            "/sessions",
            post(handlers::sessions::create_session).get(handlers::sessions::list_sessions),
        )
        .route(
            "/sessions/{id}",
            get(handlers::sessions::get_session)
                .put(handlers::sessions::update_session)
                .delete(handlers::sessions::delete_session),
        )
        .route(
            "/sessions/{id}/generate-title",
            post(handlers::sessions::generate_title),
        )
        // Messages
        .route(
            "/sessions/{id}/messages",
            get(handlers::messages::get_messages).post(handlers::messages::send_message),
        )
        .route(
            "/sessions/{id}/messages/{message_id}/and-after",
            delete(handlers::messages::delete_messages_from),
        )
        // Chat
        .route("/chat", post(handlers::chat::chat))
        // Memory
        .route(
            "/memory",
            post(handlers::memory::create_memory).get(handlers::memory::recall_memories),
        )
        .route(
            "/memory/{key}",
            get(handlers::memory::read_memory_by_key)
                .put(handlers::memory::update_memory)
                .delete(handlers::memory::delete_memory),
        )
        // Wiki — static paths must precede the /{slug} dynamic segment
        .route("/wiki", get(handlers::wiki::list_wiki_pages))
        .route("/wiki/search", get(handlers::wiki::search_wiki_pages))
        .route("/wiki/ingest", post(handlers::wiki::ingest_wiki_source))
        .route("/wiki/upload", post(handlers::wiki::upload_wiki_source))
        .route("/wiki/sync", post(handlers::wiki::sync_wiki_to_memory))
        .route("/wiki/graph", get(handlers::wiki::get_wiki_graph))
        .route("/wiki/query", post(handlers::wiki::query_wiki))
        .route("/wiki/lint", post(handlers::wiki::lint_wiki))
        .route(
            "/wiki/prompt",
            get(handlers::wiki::get_wiki_prompt).put(handlers::wiki::set_wiki_prompt),
        )
        .route(
            "/wiki/sources",
            get(handlers::wiki::list_wiki_sources).delete(handlers::wiki::delete_all_wiki_sources),
        )
        .route(
            "/wiki/sources/{filename}",
            delete(handlers::wiki::delete_wiki_source),
        )
        .route(
            "/wiki/sources/{filename}/regenerate",
            post(handlers::wiki::regenerate_wiki_source),
        )
        .route("/wiki/pages", delete(handlers::wiki::delete_wiki_pages))
        .route(
            "/wiki/pages/{slug}",
            delete(handlers::wiki::delete_wiki_page),
        )
        .route("/wiki/regenerate", post(handlers::wiki::regenerate_wiki))
        .route("/wiki/dir", get(handlers::wiki::get_wiki_dir))
        .route(
            "/wiki/converter/status",
            get(handlers::wiki::get_converter_status),
        )
        .route("/wiki/{slug}", get(handlers::wiki::get_wiki_page))
        // Config
        .route(
            "/config",
            get(handlers::config::get_config).put(handlers::config::update_config),
        )
        .route("/config/file", get(handlers::config::get_config_file))
        // Setup / onboarding
        .route("/setup/status", get(handlers::config::setup_status))
        // Credentials (Phase 8)
        .route(
            "/credentials",
            post(handlers::credentials::set_credential)
                .get(handlers::credentials::list_credentials),
        )
        .route(
            "/credentials/{key}",
            delete(handlers::credentials::delete_credential),
        )
        .route(
            "/credentials/{key}/value",
            get(handlers::credentials::get_credential_value),
        )
        .route(
            "/credentials/{key}/exists",
            get(handlers::credentials::credential_exists),
        )
        // Providers (Phase 8 -- multi-provider)
        .route(
            "/providers",
            get(handlers::providers::list_providers)
                .post(handlers::providers::create_user_provider),
        )
        .route(
            "/providers/with-key-status",
            get(handlers::providers::list_with_key_status),
        )
        .route(
            "/providers/default",
            get(handlers::providers::get_default_model).put(handlers::providers::set_default_model),
        )
        .route(
            "/providers/{id}",
            get(handlers::providers::get_provider)
                .put(handlers::providers::update_provider)
                .delete(handlers::providers::delete_user_provider),
        )
        .route(
            "/providers/{id}/test",
            post(handlers::providers::test_connection),
        )
        .route(
            "/providers/{id}/models",
            post(handlers::providers::add_model),
        )
        .route(
            "/providers/{id}/models/{model_id}",
            delete(handlers::providers::delete_model),
        )
        // Tools
        .route("/tools", get(handlers::tools::list_tools))
        .route("/tools/{name}/execute", post(handlers::tools::execute_tool))
        // Permissions (Phase 19)
        .route("/permissions", get(handlers::permissions::list_surfaces))
        .route(
            "/permissions/{surface}",
            get(handlers::permissions::get_permissions),
        )
        .route(
            "/permissions/{surface}/{tool}",
            put(handlers::permissions::set_permission)
                .delete(handlers::permissions::delete_permission),
        )
        // System info
        .route("/system/info", get(handlers::system::system_info))
        // Models
        .route("/models", get(handlers::models::list_models))
        // Identity (Phase 4)
        .route("/identity", get(handlers::identity::list_identity))
        .route(
            "/identity/reload",
            post(handlers::identity::reload_identity),
        )
        .route(
            "/identity/{name}",
            get(handlers::identity::get_identity_file)
                .put(handlers::identity::update_identity_file),
        )
        // Skills (Phase 4)
        .route(
            "/skills",
            get(handlers::skills::list_skills).post(handlers::skills::create_skill),
        )
        .route("/skills/reload", post(handlers::skills::reload_skills))
        .route(
            "/skills/{id}",
            get(handlers::skills::get_skill)
                .put(handlers::skills::update_skill)
                .delete(handlers::skills::delete_skill),
        )
        // Skill Proposals (Phase 8)
        .route(
            "/skills/proposals",
            get(handlers::skill_proposals::list_proposals),
        )
        .route(
            "/skills/proposals/{id}/approve",
            post(handlers::skill_proposals::approve_proposal),
        )
        .route(
            "/skills/proposals/{id}/reject",
            post(handlers::skill_proposals::reject_proposal),
        )
        .route(
            "/skills/proposals/{id}",
            delete(handlers::skill_proposals::delete_proposal),
        )
        // User (Phase 4)
        .route(
            "/user/observations",
            get(handlers::user::list_observations)
                .post(handlers::user::add_observation)
                .delete(handlers::user::clear_observations),
        )
        .route(
            "/user/observations/{key}",
            get(handlers::user::get_observation_by_key).delete(handlers::user::delete_observation),
        )
        .route("/user/profile", get(handlers::user::get_user_profile))
        // Embeddings (Phase 8.11)
        .route(
            "/embeddings/status",
            get(handlers::embeddings::embeddings_status),
        )
        .route(
            "/embeddings/test",
            post(handlers::embeddings::embeddings_test),
        )
        .route(
            "/embeddings/embed",
            post(handlers::embeddings::embeddings_embed),
        )
        .route(
            "/embeddings/download",
            post(handlers::embeddings::embeddings_download),
        )
        .route(
            "/embeddings/reindex",
            post(handlers::embeddings::embeddings_reindex),
        )
        // Plugins (Phase 9)
        .route("/plugins", get(handlers::plugins::list_plugins))
        .route("/plugins/install", post(handlers::plugins::install_plugin))
        .route(
            "/plugins/available",
            get(handlers::plugins::list_available_plugins),
        )
        .route(
            "/plugins/{name}",
            get(handlers::plugins::get_plugin).delete(handlers::plugins::remove_plugin),
        )
        .route(
            "/plugins/{name}/toggle",
            put(handlers::plugins::toggle_plugin),
        )
        .route(
            "/plugins/{name}/update",
            post(handlers::plugins::update_plugin),
        )
        .route(
            "/plugins/{name}/config",
            get(handlers::plugins::get_plugin_config).put(handlers::plugins::update_plugin_config),
        )
        // PAR: Runtime doctor/install (own state, baked in via with_state)
        .merge(runtimes_routes(state.runtime_manager.clone()))
        // Channel credential test (always available, no feature gate)
        .route(
            "/channels/{name}/test",
            post(handlers::channels_test::test_channel_credentials),
        )
        // Channels (Phase 8)
        .merge(channel_routes())
        // Scheduler (Phase 8)
        .merge(scheduler_routes())
        // Workflows (Feature 5)
        .merge(workflow_routes())
        // Agent Delegation
        .route(
            "/agents/active",
            get(handlers::delegation::list_active_agents),
        )
        .route(
            "/agents/{id}/cancel",
            post(handlers::delegation::cancel_agent),
        )
        // Approvals
        .route("/approvals/rules", get(handlers::approvals::list_rules))
        .route(
            "/approvals/rules/{id}",
            delete(handlers::approvals::delete_rule),
        )
        .route(
            "/approvals/{id}/respond",
            post(handlers::approvals::respond_approval),
        )
        // WebSocket
        .route("/ws/chat", get(handlers::ws::ws_chat))
        .route("/ws/notifications", get(handlers::ws::ws_notifications))
        // API Documentation (feature-gated)
        .merge(api_docs_routes())
        // Auth middleware
        .layer(middleware::from_fn_with_state(
            state.config.load().gateway_auth_token.clone(),
            auth_middleware,
        ))
        // CORS
        .layer(cors)
        // Tracing
        .layer(TraceLayer::new_for_http())
        .with_state(state)
}

/// Build PAR runtime routes with their own `Arc<RuntimeManager>` state baked in.
fn runtimes_routes(manager: Arc<crate::runtimes::RuntimeManager>) -> Router<Arc<AppState>> {
    Router::new()
        .route("/runtimes/status", get(handlers::runtimes::runtime_status))
        .route(
            "/runtimes/{name}/install",
            post(handlers::runtimes::runtime_install),
        )
        .route(
            "/runtimes/recheck",
            post(handlers::runtimes::runtime_recheck),
        )
        .with_state(manager)
}

/// Build channel routes, conditionally compiled.
fn channel_routes() -> Router<Arc<AppState>> {
    #[cfg(feature = "channels")]
    {
        Router::new()
            .route(
                "/channels/sessions",
                get(handlers::channels::list_channel_sessions),
            )
            .route(
                "/channels/sessions/{id}/messages",
                get(handlers::channels::list_channel_messages),
            )
            .route("/channels", get(handlers::channels::list_channels))
            .route(
                "/channels/{name}/status",
                get(handlers::channels::channel_status),
            )
            .route(
                "/channels/{name}/send",
                post(handlers::channels::send_message),
            )
            .route(
                "/channels/{name}/connect",
                post(handlers::channels::connect_channel),
            )
            .route(
                "/channels/{name}/disconnect",
                post(handlers::channels::disconnect_channel),
            )
            .route(
                "/channels/{name}/health",
                get(handlers::channels::health_check),
            )
            .route(
                "/channels/{name}/message",
                post(handlers::channels::webhook_message),
            )
    }
    #[cfg(not(feature = "channels"))]
    {
        Router::new()
    }
}

/// Build scheduler routes, conditionally compiled.
fn scheduler_routes() -> Router<Arc<AppState>> {
    #[cfg(feature = "scheduler")]
    {
        use axum::routing::put;
        Router::new()
            .route(
                "/scheduler/jobs",
                get(handlers::scheduler::list_jobs).post(handlers::scheduler::create_job),
            )
            .route(
                "/scheduler/jobs/{id}/toggle",
                put(handlers::scheduler::toggle_job),
            )
            .route(
                "/scheduler/jobs/{id}",
                put(handlers::scheduler::update_job).delete(handlers::scheduler::delete_job),
            )
            .route(
                "/scheduler/jobs/{id}/history",
                get(handlers::scheduler::job_history),
            )
            .route(
                "/scheduler/status",
                get(handlers::scheduler::scheduler_status),
            )
    }
    #[cfg(not(feature = "scheduler"))]
    {
        Router::new()
    }
}

/// Build API docs routes, conditionally compiled.
fn api_docs_routes() -> Router<Arc<AppState>> {
    #[cfg(feature = "api-docs")]
    {
        super::openapi::openapi_routes()
    }
    #[cfg(not(feature = "api-docs"))]
    {
        Router::new()
    }
}

/// Build workflow routes, conditionally compiled.
fn workflow_routes() -> Router<Arc<AppState>> {
    #[cfg(feature = "workflows")]
    {
        Router::new()
            .route(
                "/workflows/generate",
                post(handlers::workflows::generate_workflow),
            )
            .route(
                "/workflows",
                get(handlers::workflows::list_workflows).post(handlers::workflows::create_workflow),
            )
            .route(
                "/workflows/{id}",
                get(handlers::workflows::get_workflow)
                    .put(handlers::workflows::update_workflow)
                    .delete(handlers::workflows::delete_workflow),
            )
            .route(
                "/workflows/{id}/raw",
                get(handlers::workflows::get_workflow_raw),
            )
            .route(
                "/workflows/{id}/run",
                post(handlers::workflows::run_workflow),
            )
            .route(
                "/workflows/{id}/runs/{run_id}/cancel",
                post(handlers::workflows::cancel_workflow_run),
            )
            .route(
                "/workflows/{id}/history",
                get(handlers::workflows::workflow_history),
            )
            .route(
                "/workflows/{id}/runs/{run_id}",
                get(handlers::workflows::get_run_details),
            )
    }
    #[cfg(not(feature = "workflows"))]
    {
        Router::new()
    }
}

fn build_cors(origins: &[String]) -> CorsLayer {
    if origins.iter().any(|o| o == "*") {
        CorsLayer::permissive()
    } else if origins.is_empty() {
        // Empty origins = deny all cross-origin requests (safe default)
        CorsLayer::new()
            .allow_origin(AllowOrigin::list(Vec::<axum::http::HeaderValue>::new()))
            .allow_methods(tower_http::cors::Any)
            .allow_headers(tower_http::cors::Any)
    } else {
        let origins: Vec<_> = origins.iter().filter_map(|o| o.parse().ok()).collect();
        CorsLayer::new()
            .allow_origin(AllowOrigin::list(origins))
            .allow_methods(tower_http::cors::Any)
            .allow_headers(tower_http::cors::Any)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use axum::body::Body;
    use axum::http::{Request, StatusCode};
    use tower::ServiceExt;

    async fn test_state() -> (tempfile::TempDir, Arc<AppState>) {
        crate::gateway::handlers::tests::test_state().await
    }

    // 4.1.1 — invalid route returns 404
    #[tokio::test]
    async fn invalid_route_returns_404() {
        let (_dir, state) = test_state().await;
        let app = build_router(state);

        let req = Request::builder()
            .uri("/nonexistent")
            .body(Body::empty())
            .unwrap();

        let resp = app.oneshot(req).await.unwrap();
        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
    }

    // 4.1.2 — CORS headers present
    #[tokio::test]
    async fn cors_headers_present() {
        let (_dir, state) = test_state().await;
        let app = build_router(state);

        let req = Request::builder()
            .uri("/health")
            .header("origin", "http://localhost:18971")
            .body(Body::empty())
            .unwrap();

        let resp = app.oneshot(req).await.unwrap();
        assert_eq!(resp.status(), StatusCode::OK);
        assert!(resp.headers().get("access-control-allow-origin").is_some());
    }

    // 4.1.3 — OPTIONS preflight returns 200
    #[tokio::test]
    async fn options_preflight_returns_200() {
        let (_dir, state) = test_state().await;
        let app = build_router(state);

        let req = Request::builder()
            .method("OPTIONS")
            .uri("/sessions")
            .header("origin", "http://localhost:18971")
            .header("access-control-request-method", "POST")
            .body(Body::empty())
            .unwrap();

        let resp = app.oneshot(req).await.unwrap();
        assert_eq!(resp.status(), StatusCode::OK);
    }
}
