use std::sync::Arc;
use std::time::Duration;

use nameth::NamedEnumValues as _;
use nameth::nameth;
use tokio::time::error::Elapsed;
use tracing::Instrument as _;
use tracing::info;
use tracing::info_span;
use trz_gateway_common::certificate_info::CertificateInfo;
use trz_gateway_common::handle::ServerHandle;
use trz_gateway_common::handle::ServerStopError;
use trz_gateway_common::id::ClientName;
use trz_gateway_common::security_configuration::certificate::cache::CachedCertificate;
use trz_gateway_server::auth_code::AuthCode;
use trz_gateway_server::server::GatewayError;
use trz_gateway_server::server::Server;

use super::test_client_config::TestClientConfig;
use super::test_gateway_config::TestGatewayConfig;
use super::test_gateway_config::use_temp_dir;
use super::test_tunnel_config::TestTunnelConfig;
use crate::client::Client;
use crate::client::NewClientError;
use crate::client::connect::ConnectError;
use crate::load_client_certificate::LoadClientCertificateError;
use crate::load_client_certificate::load_client_certificate;
use crate::tests::test_gateway_config::lock_temp_dir;

const CLIENT_CERT_FILENAME: CertificateInfo<&str> = CertificateInfo {
    certificate: "client-cert.pem",
    private_key: "client-key.pem",
};

pub struct EndToEnd<'t> {
    pub client: Arc<Client>,
    #[expect(unused)]
    pub client_certificate: CachedCertificate,
    pub server: Arc<Server>,
    pub client_handle: Box<dyn FnOnce() -> ServerHandle<()> + 't>,
}

impl<'t> EndToEnd<'t> {
    pub async fn run(
        test: impl AsyncFnOnce(EndToEnd) -> Result<(), Box<dyn std::error::Error>> + Send,
    ) -> Result<(), EndToEndError> {
        let temp_dir = use_temp_dir();
        let temp_dir_lock = lock_temp_dir();

        let gateway_config = TestGatewayConfig::new();
        let (server, server_handle, _crash) = Server::run(gateway_config.clone())
            .instrument(info_span!("TestServer"))
            .await
            .map_err(EndToEndError::SetupServer)?;
        info!("Started the server");

        let client_name = ClientName::from("EndToEndClient");
        let client_config = TestClientConfig::new(gateway_config.clone(), client_name.clone());

        let auth_code = AuthCode::current().to_string();
        let client_certificate = load_client_certificate(
            &client_config,
            auth_code.into(),
            CLIENT_CERT_FILENAME.map(|filename| temp_dir.path().join(filename)),
        )
        .await
        .map_err(EndToEndError::LoadClientCertificate)?;
        info!("Got the client certificate");

        let tunnel_config = TestTunnelConfig::new(client_config, client_certificate.clone());
        let client = Client::new(tunnel_config).map_err(EndToEndError::NewClient)?;
        let client_handle = client
            .run()
            .instrument(info_span!("TestClient"))
            .await
            .map_err(EndToEndError::RunClientError)?;
        info!("The client is running");

        let mut client_handle = Some(client_handle);

        let test = test(EndToEnd {
            client,
            server,
            client_certificate,
            client_handle: Box::new(|| client_handle.take().unwrap()),
        });
        let test = tokio::time::timeout(Duration::from_secs(60), test);
        drop(temp_dir_lock);
        let () = test
            .await
            .map_err(EndToEndError::TestTimeout)?
            .map_err(EndToEndError::TestFailure)?;

        let () = server_handle
            .stop("Stopping server")
            .await
            .map_err(EndToEndError::StopServer)?;
        info!("Server stopped");
        if let Some(client_handle) = client_handle.take() {
            let () = client_handle
                .stop("Stopping client")
                .await
                .map_err(EndToEndError::StopClient)?;
            info!("Client stopped");
        }
        drop(temp_dir);
        Ok(())
    }
}

#[nameth]
#[derive(thiserror::Error, Debug)]
pub enum EndToEndError {
    #[error("[{n}] {0}", n = self.name())]
    SetupServer(GatewayError<Arc<TestGatewayConfig>>),

    #[error("[{n}] {0}", n = self.name())]
    LoadClientCertificate(LoadClientCertificateError<TestClientConfig<Arc<TestGatewayConfig>>>),

    #[error("[{n}] {0}", n = self.name())]
    NewClient(NewClientError<TestTunnelConfig<Arc<TestGatewayConfig>>>),

    #[error("[{n}] {0}", n = self.name())]
    RunClientError(ConnectError),

    #[error("[{n}] {0}", n = self.name())]
    TestTimeout(Elapsed),

    #[error("[{n}] {0}", n = self.name())]
    TestFailure(Box<dyn std::error::Error>),

    #[error("[{n}] {0}", n = self.name())]
    StopServer(ServerStopError),

    #[error("[{n}] {0}", n = self.name())]
    StopClient(ServerStopError),
}
