//! Redirector host configuration.
//!
//! Endpoints resolve through `openfut-host-config`, the single environment
//! reader, so this host and the Blaze sidecar build client-visible addresses by
//! the same construction path. Only the transport settings — listener, cert,
//! key, TLS knobs — are parsed here, and none of them are client-visible.

use std::time::Duration;

use openfut_adapter_fifa17::blaze::AdapterConfig;
use openfut_host_config::{self as hostcfg, ConfigError};

use openfut_tls::{ProtocolVersion, TlsConfig};

/// How long the oracle holds a redirector connection open after responding
/// (`time.sleep(0.3)` in `blaze_responder_v3b.redir_handle`).
///
/// Measured, not guessed: a differential lifecycle probe records the Python
/// redirector holding the socket ~300ms and the first version of this host
/// closing at 0ms. FIFA 17 was proven against the former, so it is the default
/// here. Not a tuning knob — changing it changes what this host is a
/// reimplementation OF.
pub const ORACLE_CLOSE_DWELL: Duration = Duration::from_millis(300);

/// Compose the shared TLS listener from the FIFA 17 adapter's observed profile.
///
/// The split is deliberate: `openfut-tls` knows how to build an acceptor,
/// `openfut-adapter-fifa17` knows what FIFA 17 offers, and this host knows only
/// how to join them. A second host serving the same client uses this same pair
/// rather than repeating either half — one configuration path is the structural
/// guard against the 2026-08-11 certificate mismatch, where two services
/// configured TLS separately and disagreed.
fn fifa17_tls(cert: impl Into<String>, key: impl Into<String>) -> Result<TlsConfig, ConfigError> {
    use openfut_adapter_fifa17::tls as profile;
    let ver = |s: &str| {
        ProtocolVersion::parse(s).map_err(|e| ConfigError(format!("FIFA 17 TLS profile: {e}")))
    };
    Ok(TlsConfig::new(
        cert,
        key,
        profile::CIPHER_LIST,
        ver(profile::MIN_VERSION)?,
        ver(profile::MAX_VERSION)?,
    ))
}

#[derive(Debug, Clone)]
pub struct RedirectorConfig {
    /// BIND: where this listener binds. Never client-visible.
    pub listen_addr: String,
    /// Required, with no default, so the host can never collide with the
    /// Python redirector it runs beside.
    pub listen_port: u16,
    pub tls: TlsConfig,
    /// ADVERTISE and everything derived from it.
    pub adapter: AdapterConfig,
    /// Dwell before closing an answered connection. Overridable ONLY so the
    /// causal experiment — set it to 0 and confirm the failure returns — can be
    /// run without rebuilding.
    pub close_dwell: Duration,
}

impl RedirectorConfig {
    pub fn from_env() -> Result<RedirectorConfig, ConfigError> {
        let adapter = hostcfg::adapter_from_env()?;

        let listen_port = hostcfg::required_port(
            "OPENFUT_REDIRECTOR_HOST_PORT",
            "this host runs beside the working Python redirector and must not \
             collide with it, so the port is explicit and has no default",
        )?;
        let listen_addr =
            hostcfg::optional("OPENFUT_REDIRECTOR_HOST_BIND", &adapter.endpoints.bind);

        let cert = hostcfg::required(
            "OPENFUT_REDIRECTOR_CERT",
            "path to the RSA certificate; reuse the proven redirector's so TLS \
             implementation stays the only variable in an A/B",
        )?;
        let key = hostcfg::required("OPENFUT_REDIRECTOR_KEY", "path to the matching private key")?;

        // FIFA 17's observed profile, supplied by the adapter. The transport
        // host chooses no cipher and no protocol window of its own: those are
        // facts about the client, and belong with the game they describe.
        let mut tls = fifa17_tls(cert, key)?;
        if let Some(list) = hostcfg::optional_opt("OPENFUT_REDIRECTOR_CIPHERS") {
            tls.cipher_list = list;
        }
        // Overridable so a failed retail handshake is a configuration change,
        // not a code change — but never lowered pre-emptively.
        if let Some(level) = hostcfg::optional_opt("OPENFUT_REDIRECTOR_SECURITY_LEVEL") {
            tls.security_level = level.trim().parse().ok();
        }

        let close_dwell = match hostcfg::optional_opt("OPENFUT_REDIRECTOR_CLOSE_DWELL_MS") {
            None => ORACLE_CLOSE_DWELL,
            Some(v) => Duration::from_millis(v.trim().parse().map_err(|_| {
                ConfigError(format!(
                    "OPENFUT_REDIRECTOR_CLOSE_DWELL_MS is not a number of milliseconds: {v:?}"
                ))
            })?),
        };

        Ok(RedirectorConfig {
            listen_addr,
            listen_port,
            tls,
            adapter,
            close_dwell,
        })
    }

    pub fn listen_on(&self) -> String {
        format!("{}:{}", self.listen_addr, self.listen_port)
    }

    /// Test fixture: the oracle's certificate, an ephemeral port, and an
    /// explicit advertised address.
    #[doc(hidden)]
    pub fn for_test(advertise: &str) -> RedirectorConfig {
        let base = concat!(env!("CARGO_MANIFEST_DIR"), "/../fifa17-recon/tools");
        RedirectorConfig {
            listen_addr: "127.0.0.1".into(),
            listen_port: 0,
            tls: fifa17_tls(
                format!("{base}/redir_cert.pem"),
                format!("{base}/redir_key.pem"),
            )
            .expect("the adapter's TLS profile must be valid"),
            adapter: AdapterConfig::advertising(advertise),
            close_dwell: ORACLE_CLOSE_DWELL,
        }
    }
}
