use super::handler::ModelHandler;
use super::{local, server};
use crate::{BindAddress, ConnectAddress, Error, Result, ServeOptions};

pub struct ModelWorker<H> {
    handler: H,
}

impl<H> ModelWorker<H> {
    pub fn new(handler: H) -> Self {
        Self { handler }
    }
}

impl<H: ModelHandler + 'static> ModelWorker<H> {
    pub fn run_local(self, env_address: &str, token: &str) -> Result<()> {
        self.run_local_to(ConnectAddress::parse(env_address)?, token)
    }

    pub fn run_local_for_episodes(
        self,
        env_address: &str,
        token: &str,
        max_episodes: u64,
    ) -> Result<()> {
        self.run_local_to_with_max_episodes(
            ConnectAddress::parse(env_address)?,
            token,
            Some(max_episodes),
        )
    }

    pub fn run_local_to(self, env_address: ConnectAddress, token: &str) -> Result<()> {
        self.run_local_to_with_max_episodes(env_address, token, None)
    }

    fn run_local_to_with_max_episodes(
        self,
        env_address: ConnectAddress,
        token: &str,
        max_episodes: Option<u64>,
    ) -> Result<()> {
        let runtime = tokio::runtime::Runtime::new()
            .map_err(|err| Error::Internal(format!("failed to create tokio runtime: {err}")))?;
        runtime.block_on(self.run_local_to_async_with_max_episodes(
            env_address,
            token,
            max_episodes,
        ))
    }

    pub async fn run_local_async(self, env_address: &str, token: &str) -> Result<()> {
        self.run_local_to_async(ConnectAddress::parse(env_address)?, token)
            .await
    }

    pub async fn run_local_to_async(self, env_address: ConnectAddress, token: &str) -> Result<()> {
        self.run_local_to_async_with_max_episodes(env_address, token, None)
            .await
    }

    pub async fn run_local_async_for_episodes(
        self,
        env_address: &str,
        token: &str,
        max_episodes: u64,
    ) -> Result<()> {
        self.run_local_to_async_for_episodes(
            ConnectAddress::parse(env_address)?,
            token,
            max_episodes,
        )
        .await
    }

    pub async fn run_local_to_async_for_episodes(
        self,
        env_address: ConnectAddress,
        token: &str,
        max_episodes: u64,
    ) -> Result<()> {
        self.run_local_to_async_with_max_episodes(env_address, token, Some(max_episodes))
            .await
    }

    async fn run_local_to_async_with_max_episodes(
        mut self,
        env_address: ConnectAddress,
        _token: &str,
        max_episodes: Option<u64>,
    ) -> Result<()> {
        let result = local::run_local_to_async_with_max_episodes(
            &mut self.handler,
            env_address,
            max_episodes,
        )
        .await;
        let close_result = self.handler.on_close().await;
        match (result, close_result) {
            (Ok(_), Ok(())) => Ok(()),
            (Err(err), Ok(())) => Err(err),
            (Ok(_), Err(err)) => Err(err),
            (Err(run_err), Err(close_err)) => Err(Error::Internal(format!(
                "local model run failed: {run_err}; close hook failed: {close_err}"
            ))),
        }
    }

    pub fn serve(self, address: &str, token: &str) -> Result<()> {
        self.serve_to(BindAddress::parse(address)?, token)
    }

    pub fn serve_to(self, address: BindAddress, token: &str) -> Result<()> {
        let runtime = tokio::runtime::Runtime::new()
            .map_err(|err| Error::Internal(format!("failed to create tokio runtime: {err}")))?;
        runtime.block_on(self.serve_to_async(address, token))
    }

    pub async fn serve_async(self, address: &str, token: &str) -> Result<()> {
        self.serve_to_async(BindAddress::parse(address)?, token)
            .await
    }

    pub async fn serve_to_async(self, address: BindAddress, token: &str) -> Result<()> {
        self.serve_to_async_with_options(address, token, ServeOptions::default())
            .await
    }

    pub fn serve_with_options(
        self,
        address: &str,
        token: &str,
        options: ServeOptions,
    ) -> Result<()> {
        self.serve_to_with_options(BindAddress::parse(address)?, token, options)
    }

    pub fn serve_to_with_options(
        self,
        address: BindAddress,
        token: &str,
        options: ServeOptions,
    ) -> Result<()> {
        let runtime = tokio::runtime::Runtime::new()
            .map_err(|err| Error::Internal(format!("failed to create tokio runtime: {err}")))?;
        runtime.block_on(self.serve_to_async_with_options(address, token, options))
    }

    pub async fn serve_async_with_options(
        self,
        address: &str,
        token: &str,
        options: ServeOptions,
    ) -> Result<()> {
        self.serve_to_async_with_options(BindAddress::parse(address)?, token, options)
            .await
    }

    pub async fn serve_to_async_with_options(
        self,
        address: BindAddress,
        token: &str,
        options: ServeOptions,
    ) -> Result<()> {
        server::serve_model_with_options(self.handler, address, token, options).await
    }
}
