use secrecy::SecretString;
use thiserror::Error;
use tokio::task::JoinHandle;

use crate::announce::{AnnounceError, send_announce};
use crate::error::ConfigError;
#[cfg(feature = "metrics")]
use crate::error::MetricsError;
use crate::ext::OrchestratorInitExt;
use crate::handshake::{InitError, read_init};
use crate::ipc::process_ipc_messages;
use crate::ipc_types::ListenAddress;
use crate::logging::init_module;
use crate::secrets::{ModuleSecretError, get_module_secret_client, get_secret};
use crate::state::AppState;
use crate::{AnnouncedEndpoint, ModuleAnnounce, OrchestratorInit};
use axum::Router;

/// Errors that may occur during server bootstrap and run.
#[derive(Debug, Error)]
pub enum ServeError {
    #[error("handshake failed: {0}")]
    Init(#[from] InitError),

    #[error("secret client error: {0}")]
    Secret(#[from] ModuleSecretError),

    #[error("announcement error: {0}")]
    Announce(#[from] AnnounceError),

    #[error("configuration error: {0}")]
    Config(#[from] ConfigError),

    #[error("I/O error: {0}")]
    Io(#[from] std::io::Error),

    #[error("server error: {0}")]
    Server(#[from] hyper::Error),

    #[cfg(feature = "metrics")]
    #[error("metrics error: {0}")]
    Metrics(#[from] MetricsError),

    #[error("unsupported listen address type: {0}")]
    UnsupportedAddressType(String),
}

/// Bootstraps and serves a PyWatt module with Axum router in one step.
///
/// - `secret_keys`: initial secret keys to fetch
/// - `endpoints`: endpoints to announce
/// - `state_builder`: builds user state from init blob and fetched secrets
/// - `router_builder`: builds an Axum router from the shared `AppState`
pub async fn serve_module<T>(
    secret_keys: Vec<String>,
    endpoints: Vec<AnnouncedEndpoint>,
    state_builder: impl Fn(&OrchestratorInit, Vec<SecretString>) -> T,
    router_builder: impl Fn(AppState<T>) -> Router,
) -> Result<(), ServeError> {
    // 1. Init logging
    init_module();

    // 2. Handshake
    let init: OrchestratorInit = read_init().await?;

    // 3. Secret client
    let client = get_module_secret_client(&init.orchestrator_api, &init.module_id).await?;

    // 4. Fetch initial secrets
    let mut secrets = Vec::new();
    for key in &secret_keys {
        let s = get_secret(&client, key).await?;
        secrets.push(s.clone());
    }

    // 5. Build AppState
    let user_state = state_builder(&init, secrets);
    let app_state = AppState::new(
        init.module_id.clone(),
        init.orchestrator_api.clone(),
        client.clone(),
        user_state,
    );

    // 6. Announce
    let announce = ModuleAnnounce {
        listen: init.listen_to_string(),
        endpoints,
    };
    send_announce(&announce)?;

    // 7. Start IPC loop
    let _ipc_handle: JoinHandle<()> = tokio::spawn(process_ipc_messages());

    // 8. Build router
    let app: Router = router_builder(app_state);

    // Serve over configured transport - only TCP is supported
    match init.listen {
        ListenAddress::Tcp(addr) => {
            serve_tcp(app.clone(), addr).await?;
            Ok(())
        }
        ListenAddress::Unix(path) => {
            tracing::error!(path = ?path, "Unix Domain Socket listeners are not supported.");
            Err(ServeError::UnsupportedAddressType(
                "Unix Domain Socket".to_string(),
            ))
        }
    }
}

/// Serve over TCP using axum::serve
async fn serve_tcp(app: Router, addr: std::net::SocketAddr) -> Result<(), ServeError> {
    let listener = tokio::net::TcpListener::bind(addr).await?;
    axum::serve(listener, app.into_make_service()).await?;
    Ok(())
}
