use crate::config;
use crate::services::item_service::ItemService;
use anyhow::Result;
use axum::Router;
use axum::http::{HeaderValue, header};
use axum::middleware::Next;
use axum::response::Response;
use clap::Command;
use log::{debug, info};
use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::Mutex;
use tower::ServiceBuilder;
use tower_http::cors::CorsLayer;
use tower_http::trace::TraceLayer;

mod api;
pub mod auth;
pub mod common;
mod pages;

pub use common::{AppState, create_auth_middleware, logging_middleware};

/// Adds security headers to all responses.
async fn security_headers(req: axum::extract::Request, next: Next) -> Response {
    let mut response = next.run(req).await;
    let headers = response.headers_mut();
    headers.insert(
        header::X_CONTENT_TYPE_OPTIONS,
        HeaderValue::from_static("nosniff"),
    );
    headers.insert(header::X_FRAME_OPTIONS, HeaderValue::from_static("DENY"));
    headers.insert(
        header::REFERRER_POLICY,
        HeaderValue::from_static("strict-origin-when-cross-origin"),
    );
    response
}

pub fn mode_server(
    cmd: &mut Command,
    settings: &config::Settings,
    conn: &mut rusqlite::Connection,
    data_path: PathBuf,
) -> Result<()> {
    // Get server address from args or config with default
    let server_address = if let Some(addr) = &settings.server_address() {
        addr.clone()
    } else if let Some(server_config) = &settings.server {
        server_config
            .address
            .clone()
            .unwrap_or_else(|| "127.0.0.1".to_string())
    } else {
        "127.0.0.1".to_string()
    };

    // Get server port from args or config with default
    let server_port = if let Some(port) = settings.server_port() {
        port
    } else if let Some(server_config) = &settings.server {
        server_config.port.unwrap_or(21080)
    } else {
        21080
    };

    let server_config = common::ServerConfig {
        address: server_address,
        port: Some(server_port),
        username: settings.server_username(),
        password: settings.server_password(),
        password_hash: settings.server_password_hash(),
        jwt_secret: settings.server_jwt_secret(),
        cert_file: settings.server_cert_file(),
        key_file: settings.server_key_file(),
        cors_origin: settings.server_cors_origin(),
    };

    // Create ItemService once
    let item_service = ItemService::new(data_path.clone());

    // We need to move the connection into the async runtime
    let rt = tokio::runtime::Runtime::new()?;
    // Take ownership of the connection and move it into the async runtime
    let owned_conn = std::mem::replace(conn, rusqlite::Connection::open_in_memory()?);
    let cmd = cmd.clone();
    let settings = settings.clone();
    rt.block_on(run_server(
        server_config,
        owned_conn,
        data_path,
        item_service,
        cmd,
        settings,
    ))
}

async fn run_server(
    config: common::ServerConfig,
    conn: rusqlite::Connection,
    data_dir: PathBuf,
    item_service: ItemService,
    _cmd: Command,
    settings: config::Settings,
) -> Result<()> {
    // Construct address with port
    let bind_address = if let Some(port) = config.port {
        format!("{}:{}", config.address, port)
    } else {
        format!("{}:21080", config.address)
    };

    debug!("SERVER: Starting REST HTTP server on {bind_address}");

    // Use the existing database connection
    let db_conn = Arc::new(Mutex::new(conn));

    let state = AppState {
        db: db_conn,
        data_dir: data_dir.clone(),
        item_service: Arc::new(item_service),
        cmd: Arc::new(Mutex::new(Command::new("keep"))),
        settings: Arc::new(settings.clone()),
    };

    let protected_router = Router::new()
        .merge(api::add_routes(Router::new()))
        .merge(pages::add_routes(Router::new()))
        .layer(axum::middleware::from_fn(create_auth_middleware(
            config.username.clone(),
            config.password.clone(),
            config.password_hash.clone(),
            config.jwt_secret.clone(),
        )));

    // Build CORS layer - restricted by default, configurable via cors_origin setting
    let cors_origin = config.cors_origin.as_deref().unwrap_or("http://localhost");
    let cors_layer = if cors_origin == "*" {
        CorsLayer::permissive()
    } else {
        CorsLayer::new()
            .allow_origin(
                cors_origin
                    .parse::<axum::http::HeaderValue>()
                    .unwrap_or_else(|_| {
                        log::warn!(
                            "Invalid CORS origin '{cors_origin}', defaulting to http://localhost"
                        );
                        "http://localhost".parse().unwrap()
                    }),
            )
            .allow_methods([
                axum::http::Method::GET,
                axum::http::Method::POST,
                axum::http::Method::PUT,
                axum::http::Method::DELETE,
            ])
            .allow_headers([header::CONTENT_TYPE, header::AUTHORIZATION, header::ACCEPT])
    };

    // Create the app with documentation routes open and others protected
    let app = Router::new()
        // Add documentation routes without authentication
        .merge(api::add_docs_routes(Router::new()))
        // Add API and pages routes with authentication
        .merge(protected_router)
        // Apply state to all routes
        .with_state(state)
        // Add other middleware layers to all routes
        .layer(axum::middleware::from_fn(security_headers))
        .layer(axum::middleware::from_fn(logging_middleware))
        .layer(
            ServiceBuilder::new()
                .layer(TraceLayer::new_for_http())
                .layer(cors_layer),
        );

    let addr: SocketAddr = bind_address.parse()?;

    // Warn if authentication is enabled without TLS
    if config.password.is_some() || config.password_hash.is_some() || config.jwt_secret.is_some() {
        #[cfg(not(feature = "tls"))]
        log::warn!(
            "SECURITY: Authentication enabled but TLS support is not compiled in. Credentials will be transmitted in plain text!"
        );
        #[cfg(feature = "tls")]
        if config.cert_file.is_none() || config.key_file.is_none() {
            log::warn!(
                "SECURITY: Authentication enabled but TLS is not configured. Credentials will be transmitted in plain text!"
            );
        }
    }

    // Build the app into a service
    let service = app.into_make_service_with_connect_info::<SocketAddr>();

    // Use TLS if both cert and key files are provided
    #[cfg(feature = "tls")]
    if let (Some(cert_file), Some(key_file)) = (&config.cert_file, &config.key_file) {
        info!("SERVER: HTTPS server listening on {addr}");

        use axum_server::tls_rustls::RustlsConfig;

        let tls_config = RustlsConfig::from_pem_file(cert_file, key_file)
            .await
            .map_err(|e| anyhow::anyhow!("Failed to load TLS config: {e}"))?;

        axum_server::bind_rustls(addr, tls_config)
            .serve(service)
            .await?;

        return Ok(());
    }

    info!("SERVER: HTTP server listening on {addr}");

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

    Ok(())
}
