//! Main application router creation
//!
//! This module handles the creation of the main application router with all
//! routes, static file serving, and dynamic route handling.

use std::collections::HashSet;
use std::path::PathBuf;

use axum::{extract::State, http::HeaderMap, response::IntoResponse, routing::get, Router};
use rustelo_core_lib::routing::get_routing_config;
use tower_http::services::ServeDir;

use super::handlers::fallback_handler;
use crate::app_state::AppState;
use crate::config::Config;
use crate::run::render_route_response;
use crate::run::SiteOptions;
use crate::utils::lang_detection;

/// Clone the request headers and inject the raw query string as a synthetic
/// `x-request-query` header. Shell renderers (e.g. the HTMX content grid) read
/// it via `current_request_headers()` so direct URLs like `/blog?tag=rust`
/// can pre-populate filter/search state. Standard HTTP requests carry the query
/// only in the URI, which the renderer never sees otherwise.
fn inject_query_header(headers: &HeaderMap, uri: &axum::http::Uri) -> HeaderMap {
    let mut augmented = headers.clone();
    if let Some(query) = uri.query() {
        if let Ok(value) = axum::http::HeaderValue::from_str(query) {
            augmented.insert(
                axum::http::HeaderName::from_static("x-request-query"),
                value,
            );
        }
    }
    augmented
}

/// Create the main application router with all routes and static file serving
pub fn create_main_router(
    config: &Config,
    site_options: SiteOptions,
    app_state: AppState,
) -> Result<axum::Router, Box<dyn std::error::Error>> {
    // Create a custom route handler for the root path instead of automatic Leptos
    // routing
    let app = Router::new()
        .route(
            "/",
            get(
                move |State(opts): State<SiteOptions>, headers: HeaderMap| async move {
                    // Detect language preference from cookies/headers
                    let detected_lang = lang_detection::detect_ssr_language("/", &headers);

                    // If Spanish is preferred and we're at root, redirect to Spanish home
                    if detected_lang == "es" {
                        let spanish_home = lang_detection::get_localized_url("/", "es");
                        if spanish_home != "/" {
                            return axum::response::Response::builder()
                                .status(302) // Temporary redirect
                                .header("Location", spanish_home)
                                .body(axum::body::Body::empty())
                                .unwrap()
                                .into_response();
                        }
                    }

                    // Dispatch through render_route_response so HX-Request / HX-Boosted
                    // selects between full shell and <main> fragment per profile.
                    let resolution = rustelo_core_lib::routing::engine::resolve_route("/");
                    render_route_response(opts, &resolution, &headers)
                },
            ),
        )
        // Add specific route for favicon.ico at root level
        .route(
            "/favicon.ico",
            get(|| async {
                match tokio::fs::read("public/favicon.ico").await {
                    Ok(content) => axum::response::Response::builder()
                        .header("Content-Type", "image/x-icon")
                        .header("Cache-Control", "public, max-age=86400")
                        .body(axum::body::Body::from(content))
                        .unwrap()
                        .into_response(),
                    Err(_) => axum::response::Response::builder()
                        .status(404)
                        .body(axum::body::Body::empty())
                        .unwrap()
                        .into_response(),
                }
            }),
        )
        // Add specific route for kitdigital.html to serve from public directory
        .route(
            "/kitdigital.html",
            get({
                let kitdigital_path = PathBuf::from(&config.server_dirs.public_dir)
                    .join("kitdigital.html")
                    .to_string_lossy()
                    .into_owned();
                move || async move {
                    match tokio::fs::read_to_string(&kitdigital_path).await {
                        Ok(content) => axum::response::Html(content).into_response(),
                        Err(_) => axum::response::Response::builder()
                            .status(404)
                            .body(axum::body::Body::from("404 - File not found"))
                            .unwrap()
                            .into_response(),
                    }
                }
            }),
        )
        // Add dynamic routes from SSR routing configuration
        .merge(create_dynamic_routes(site_options.clone()));
    // WASM package directory — only exists in leptos-hydration builds.
    #[cfg(feature = "leptos-hydration")]
    let app = app.nest_service(
        "/pkg",
        ServeDir::new(format!("{}/pkg", site_options.site_root))
            .append_index_html_on_directories(false),
    );
    // htmx runtime dir: HTMX_ASSETS_PATH override (set by the local-install
    // wrapper / container) falls back to <project_root>/templates/shared/htmx.
    // Mirrors HTMX_TEMPLATE_PATH for the minijinja templates — lets the runtime
    // assets live outside the site tree (e.g. ~/.local/share or /usr/local/share).
    let htmx_assets_dir = std::env::var("HTMX_ASSETS_PATH")
        .map(std::path::PathBuf::from)
        .unwrap_or_else(|_| crate::utils::resolve_from_root("templates/shared/htmx"));
    let app = app
        // Vendored htmx runtime + extensions for the htmx-ssr rendering profile.
        // Mounted unconditionally: harmless when the workspace is leptos-hydration
        // (the shell never emits <script> tags pointing here), and required when
        // any route resolves to htmx-ssr. Files are pinned by SHA-384 in
        // templates/shared/htmx/htmx.lock.toml.
        .nest_service(
            "/assets/htmx",
            ServeDir::new(htmx_assets_dir)
                .append_index_html_on_directories(false)
                .precompressed_gzip()
                .precompressed_br(),
        )
        // Serve console-logger.js from public root
        .route(
            "/console-logger.js",
            get(|| async {
                match tokio::fs::read("public/console-logger.js").await {
                    Ok(content) => axum::response::Response::builder()
                        .header("Content-Type", "application/javascript")
                        .body(axum::body::Body::from(content))
                        .unwrap()
                        .into_response(),
                    Err(_) => axum::response::Response::builder()
                        .status(404)
                        .body(axum::body::Body::empty())
                        .unwrap()
                        .into_response(),
                }
            }),
        )
        .nest_service(
            "/content",
            ServeDir::new(PathBuf::from(&config.server_dirs.public_dir).join("content"))
                .precompressed_gzip()
                .precompressed_br()
                .precompressed_deflate(),
        )
        .nest_service(
            "/public",
            ServeDir::new(&config.server_dirs.public_dir)
                .precompressed_gzip()
                .precompressed_br()
                .precompressed_deflate(),
        )
        .nest_service(
            "/styles",
            ServeDir::new(PathBuf::from(&config.server_dirs.public_dir).join("styles"))
                .precompressed_gzip()
                .precompressed_br()
                .precompressed_deflate(),
        )
        .nest_service(
            "/images",
            ServeDir::new(PathBuf::from(&config.server_dirs.public_dir).join("images"))
                .precompressed_gzip()
                .precompressed_br()
                .precompressed_deflate(),
        )
        .route(
            "/kitdigital",
            get(|| async { axum::response::Redirect::permanent("/kitdigital/").into_response() }),
        )
        .nest_service(
            "/kitdigital/",
            ServeDir::new(PathBuf::from(&config.server_dirs.public_dir).join("kitdigital"))
                .precompressed_gzip()
                .precompressed_br()
                .precompressed_deflate(),
        )
        .route(
            "/docs",
            get(|| async { axum::response::Redirect::permanent("/docs/").into_response() }),
        )
        .nest_service(
            "/docs/",
            ServeDir::new(PathBuf::from(&config.server_dirs.public_dir).join("docs"))
                .precompressed_gzip()
                .precompressed_br()
                .precompressed_deflate(),
        )
        // Slidev presentations served as static SPA
        .route(
            "/slides",
            get(|| async { axum::response::Redirect::permanent("/slides/").into_response() }),
        )
        .nest_service(
            "/slides/",
            ServeDir::new(std::env::var("SITE_SLIDES_PATH").unwrap_or_else(|_| {
                PathBuf::from(&config.server_dirs.public_dir)
                    .join("slides")
                    .to_string_lossy()
                    .into_owned()
            }))
            .append_index_html_on_directories(true)
            .precompressed_gzip()
            .precompressed_br()
            .precompressed_deflate(),
        )
        .nest_service(
            "/js",
            ServeDir::new(PathBuf::from(&config.server_dirs.public_dir).join("js"))
                .precompressed_gzip()
                .precompressed_br()
                .precompressed_deflate(),
        )
        .nest_service(
            "/css",
            ServeDir::new(PathBuf::from(&config.server_dirs.public_dir).join("css"))
                .precompressed_gzip()
                .precompressed_br()
                .precompressed_deflate(),
        );

    let app = app.nest_service(
        &std::env::var("SITE_SERVER_CONTENT_URL").unwrap_or_else(|_| "/".to_string()),
        ServeDir::new(format!(
            "{}/{}",
            site_options.site_root,
            std::env::var("SITE_SERVER_ROOT_CONTENT").unwrap_or_else(|_| "".to_string())
        ))
        .precompressed_gzip()
        .precompressed_br()
        .precompressed_deflate(),
    );

    // Content resources route will be added by dynamic content routes

    // Content routes are now handled by SITE_SERVER_CONTENT_URL configuration above

    let app = app
        // Add fallback handler for dynamic routes (content posts, etc.)
        .fallback(fallback_handler)
        .with_state(app_state.site_options.clone());

    Ok(app)
}

/// Generate dynamic routes from SSR routing configuration.
pub fn create_dynamic_routes(site_options: SiteOptions) -> Router<SiteOptions> {
    let routing_config = get_routing_config();
    let mut router = Router::new();

    // Running "already registered" set to prevent Axum from panicking when
    // multiple language entries map to the same URL (e.g. both "en" and "es"
    // use "/blog"). We insert each registered path so duplicates are skipped.
    let mut seen_paths: HashSet<String> = HashSet::new();
    // Pre-populate with paths already registered in create_main_router to prevent
    // Axum from panicking on merge (hardcoded static file handlers).
    seen_paths.insert("/".to_string());
    seen_paths.insert("/content".to_string());
    seen_paths.insert("/slides".to_string());
    seen_paths.insert("/favicon.ico".to_string());
    seen_paths.insert("/kitdigital.html".to_string());

    tracing::debug!("Leptos routes already registered: {:?}", seen_paths);

    // Get all routes from the SSR configuration, excluding those handled by Leptos
    let all_routes = &routing_config.routes;

    for route_config in all_routes {
        let path = route_config.path.clone();

        // Skip routes that are already handled by Leptos, manually handled, served
        // as static files, or already registered earlier in this loop (duplicate
        // paths from multi-language configs that share a URL).
        if seen_paths.contains(&path) {
            tracing::debug!(
                "Skipping route '{}' - already registered or handled by Leptos",
                path
            );
            continue;
        }

        // Skip external URLs and routes that don't start with '/'
        if route_config.is_external.unwrap_or(false) || !path.starts_with('/') {
            tracing::debug!(
                "Skipping external route '{}' - not suitable for internal router",
                path
            );
            continue;
        }

        let opts = site_options.clone();

        tracing::debug!("Adding dynamic SSR route: {}", path);

        // Check if this is a parametric route that needs path extraction
        if path.contains('{') && path.contains('}') {
            // Handle parametric routes - extract actual path from URI
            router = router.route(
                &path,
                axum::routing::get({
                    let template_path = path.clone();
                    let opts = opts.clone();
                    move |uri: axum::http::Uri, headers: HeaderMap| {
                        let template_path = template_path.clone();
                        let opts = opts.clone();
                        async move {
                            // Extract the actual request path from the URI
                            let actual_path = uri.path().to_string();

                            tracing::info!(
                                "🔧 Parametric route - template: '{}', actual: '{}'",
                                template_path,
                                actual_path
                            );

                            // Use the shared routing engine to resolve the route first
                            let route_resolution =
                                rustelo_core_lib::routing::engine::resolve_route(&actual_path);

                            // Check if the routing engine provides a specific component resolution
                            if route_resolution.exists && route_resolution.component.is_some() {
                                tracing::info!(
                                    "🔧 Parametric route using routing engine resolution: {:?} \
                                     with parameters: {:?}",
                                    route_resolution.component,
                                    route_resolution.parameters
                                );

                                // Detect language preference from cookies/headers for this route
                                let detected_lang =
                                    crate::utils::lang_detection::detect_ssr_language(
                                        &actual_path,
                                        &headers,
                                    );

                                // Check if we need to redirect to the localized version
                                // (language-agnostic)
                                if let Some(localized_url) =
                                    crate::utils::lang_detection::check_language_redirect(
                                        &actual_path,
                                        &detected_lang,
                                        &headers,
                                    )
                                {
                                    tracing::debug!(
                                        "Redirecting {} to localized version ({}): {}",
                                        actual_path,
                                        detected_lang,
                                        localized_url
                                    );
                                    return axum::response::Response::builder()
                                        .status(302) // Temporary redirect
                                        .header("Location", localized_url)
                                        .body(axum::body::Body::empty())
                                        .unwrap()
                                        .into_response();
                                }

                                // Dispatch through render_route_response so HX-Request /
                                // HX-Boosted selects between full shell and <main> fragment
                                // per route's rendering profile.
                                let augmented = inject_query_header(&headers, &uri);
                                return render_route_response(opts, &route_resolution, &augmented);
                            }

                            // Fallback to standard TOML-based parametric routing
                            tracing::info!(
                                "🔧 Parametric route falling back to standard TOML routing"
                            );

                            // Detect language preference from cookies/headers for this route
                            let detected_lang = crate::utils::lang_detection::detect_ssr_language(
                                &actual_path,
                                &headers,
                            );

                            // Check if we need to redirect to the localized version
                            // (language-agnostic)
                            if let Some(localized_url) =
                                crate::utils::lang_detection::check_language_redirect(
                                    &actual_path,
                                    &detected_lang,
                                    &headers,
                                )
                            {
                                tracing::debug!(
                                    "Redirecting {} to localized version ({}): {}",
                                    actual_path,
                                    detected_lang,
                                    localized_url
                                );
                                return axum::response::Response::builder()
                                    .status(302) // Temporary redirect
                                    .header("Location", localized_url)
                                    .body(axum::body::Body::empty())
                                    .unwrap()
                                    .into_response();
                            }

                            // Use the actual request path for SSR rendering, dispatching
                            // through render_route_response to honour HX-Request / HX-Boosted.
                            let augmented = inject_query_header(&headers, &uri);
                            let resolution =
                                rustelo_core_lib::routing::engine::resolve_route(&actual_path);
                            render_route_response(opts, &resolution, &augmented)
                        }
                    }
                }),
            );
            seen_paths.insert(path.clone());
        } else {
            router = router.route(
                &path,
                axum::routing::get({
                    let path = path.clone();
                    let opts = opts.clone();
                    move |uri: axum::http::Uri, headers: HeaderMap| {
                        let path = path.clone();
                        let opts = opts.clone();
                        async move {
                            // Detect language preference from cookies/headers for this route
                            let detected_lang =
                                crate::utils::lang_detection::detect_ssr_language(&path, &headers);

                            // Check if we need to redirect to the localized version
                            // (language-agnostic)
                            if let Some(localized_url) =
                                crate::utils::lang_detection::check_language_redirect(
                                    &path,
                                    &detected_lang,
                                    &headers,
                                )
                            {
                                tracing::debug!(
                                    "Redirecting {} to localized version ({}): {}",
                                    path,
                                    detected_lang,
                                    localized_url
                                );
                                return axum::response::Response::builder()
                                    .status(302) // Temporary redirect
                                    .header("Location", localized_url)
                                    .body(axum::body::Body::empty())
                                    .unwrap()
                                    .into_response();
                            }

                            // Render the page through render_route_response so
                            // HX-Request / HX-Boosted selects shell vs <main> fragment.
                            // Inject the query string so grid filters/search can
                            // pre-populate from direct URLs like /blog?tag=rust.
                            let augmented = inject_query_header(&headers, &uri);
                            let resolution =
                                rustelo_core_lib::routing::engine::resolve_route(&path);
                            render_route_response(opts, &resolution, &augmented)
                        }
                    }
                }),
            );

            seen_paths.insert(path.clone());
            tracing::debug!("✅ Registered route: {}", path);
        }
    }

    // Add additional paths that might not be in the config yet
    // but are used by the application (also checking for conflicts)
    let additional_paths = vec!["/daisyui", "/features-demo"]; // /user is now handled by TOML config

    for path in additional_paths {
        // Skip if already handled by Leptos or previously registered
        if seen_paths.contains(path) {
            tracing::debug!(
                "Skipping additional route '{}' - already handled by Leptos",
                path
            );
            continue;
        }

        let opts = site_options.clone();

        tracing::debug!("Adding additional route: {}", path);

        router = router.route(
            path,
            axum::routing::get({
                let path = path.to_string();
                let opts = opts.clone();
                move |headers: HeaderMap| {
                    let path = path.clone();
                    let opts = opts.clone();
                    async move {
                        let resolution = rustelo_core_lib::routing::engine::resolve_route(&path);
                        render_route_response(opts, &resolution, &headers)
                    }
                }
            }),
        );
    }

    router.with_state(site_options)
}
