// Code generated by TofuPilot SDK Generator. DO NOT EDIT.

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

use reqwest::header::{HeaderValue, AUTHORIZATION, USER_AGENT};

use crate::config::ClientConfig;
use crate::error::{Error, Result};
use crate::hooks::HookContext;
use crate::procedures::ProceduresClient;
use crate::versions::VersionsClient;
use crate::runs::RunsClient;
use crate::attachments::AttachmentsClient;
use crate::units::UnitsClient;
use crate::parts::PartsClient;
use crate::revisions::RevisionsClient;
use crate::batches::BatchesClient;
use crate::stations::StationsClient;
use crate::user::UserClient;

/// TofuPilot API client.
///
/// Provides access to all TofuPilot API resources with typed requests,
/// typed errors, retry logic, and request lifecycle hooks.
///
/// # Example
///
/// ```no_run
/// use tofupilot::TofuPilot;
///
/// #[tokio::main]
/// async fn main() -> tofupilot::Result<()> {
///     let client = TofuPilot::new("your-api-key");
///
///     let procedures = client.procedures().list().send().await?;
///     println!("{:?}", procedures);
///     Ok(())
/// }
/// ```
#[derive(Debug, Clone)]
pub struct TofuPilot {
    pub(crate) http: reqwest::Client,
    pub(crate) http_external: reqwest::Client,
    pub(crate) config: ClientConfig,
}

impl TofuPilot {
    /// Create a new client with the given API key.
    pub fn new(api_key: impl Into<String>) -> Self {
        Self::with_config(ClientConfig::new(api_key))
    }

    /// Create a new client with full configuration.
    pub fn with_config(config: ClientConfig) -> Self {
        let mut headers = reqwest::header::HeaderMap::new();
        headers.insert(
            AUTHORIZATION,
            HeaderValue::from_str(&format!("Bearer {}", config.api_key))
                .expect("invalid API key"),
        );
        headers.insert(
            USER_AGENT,
            HeaderValue::from_str(&config.user_agent).unwrap_or_else(|_| {
                HeaderValue::from_static("tofupilot-rust")
            }),
        );
        headers.insert(
            "x-client-type",
            HeaderValue::from_static("rust"),
        );
        headers.insert(
            "x-client-version",
            HeaderValue::from_str(env!("CARGO_PKG_VERSION")).unwrap_or_else(|_| {
                HeaderValue::from_static("unknown")
            }),
        );

        let http = reqwest::Client::builder()
            .default_headers(headers)
            .timeout(config.timeout)
            .build()
            .expect("failed to build HTTP client");

        let http_external = reqwest::Client::builder()
            .use_rustls_tls()
            .build()
            .expect("failed to build external HTTP client");

        Self { http, http_external, config }
    }

    /// Access the Procedures API.
    pub fn procedures(&self) -> ProceduresClient<'_> {
        ProceduresClient::new(self)
    }

    /// Access the Versions API.
    pub fn versions(&self) -> VersionsClient<'_> {
        VersionsClient::new(self)
    }

    /// Access the Runs API.
    pub fn runs(&self) -> RunsClient<'_> {
        RunsClient::new(self)
    }

    /// Access the Attachments API.
    pub fn attachments(&self) -> AttachmentsClient<'_> {
        AttachmentsClient::new(self)
    }

    /// Access the Units API.
    pub fn units(&self) -> UnitsClient<'_> {
        UnitsClient::new(self)
    }

    /// Access the Parts API.
    pub fn parts(&self) -> PartsClient<'_> {
        PartsClient::new(self)
    }

    /// Access the Revisions API.
    pub fn revisions(&self) -> RevisionsClient<'_> {
        RevisionsClient::new(self)
    }

    /// Access the Batches API.
    pub fn batches(&self) -> BatchesClient<'_> {
        BatchesClient::new(self)
    }

    /// Access the Stations API.
    pub fn stations(&self) -> StationsClient<'_> {
        StationsClient::new(self)
    }

    /// Access the User API.
    pub fn user(&self) -> UserClient<'_> {
        UserClient::new(self)
    }

    /// Execute a request with retry logic and hooks.
    pub(crate) async fn execute(
        &self,
        request: reqwest::RequestBuilder,
        operation_id: &str,
        base_url: &str,
    ) -> Result<reqwest::Response> {
        let hook_ctx = HookContext {
            operation_id: Arc::from(operation_id),
            base_url: Arc::from(base_url),
        };

        let mut last_error: Option<Error> = None;

        for attempt in 0..=self.config.max_retries {
            if attempt > 0 {
                let base_ms = 100u64 * 2u64.pow(attempt - 1);
                let jitter = fastrand::f64() * base_ms as f64;
                let delay = Duration::from_millis(base_ms + jitter as u64);
                tokio::time::sleep(delay).await;
            }

            let req = request
                .try_clone()
                .ok_or_else(|| Error::Validation(
                    "request cannot be cloned (streaming body)".to_string(),
                ))?;

            let built = req.build()?;

            // Run before-request hooks
            let built = self.config.hooks.run_before(&hook_ctx, built).await;

            let result = self.http.execute(built).await;

            match result {
                Ok(response) => {
                    let status = response.status().as_u16();
                    if (200..300).contains(&status) {
                        self.config.hooks.run_after_success(&hook_ctx, &response).await;
                        return Ok(response);
                    }

                    let body = response.text().await.unwrap_or_default();
                    let err = Error::from_response(status, &body);

                    if err.is_retryable() && attempt < self.config.max_retries {
                        self.config.hooks.run_after_error(&hook_ctx, &err).await;
                        last_error = Some(err);
                        continue;
                    }

                    self.config.hooks.run_after_error(&hook_ctx, &err).await;
                    return Err(err);
                }
                Err(e) => {
                    let err = Error::Http(e);
                    if attempt < self.config.max_retries {
                        self.config.hooks.run_after_error(&hook_ctx, &err).await;
                        last_error = Some(err);
                        continue;
                    }
                    self.config.hooks.run_after_error(&hook_ctx, &err).await;
                    return Err(err);
                }
            }
        }

        Err(last_error.unwrap_or_else(|| Error::UnexpectedStatus {
            status: 0,
            body: "max retries exceeded".to_string(),
        }))
    }
}
