use crate::api::jobs::{JobBuilder, JobListBuilder};
use crate::api::pipelines::{PipelineBuilder, PipelineListBuilder};
use crate::api::projects::ProjectBuilder;
use crate::api::variables::VariableBuilder;
use crate::error::{GitLabError, Result};
use gitlab::Gitlab;

/// The main client for interacting with the GitLab API.
///
/// This client provides access to all GitLab API operations including
/// pipelines, jobs, runners, merge requests, and more.
///
/// # Examples
///
/// ```no_run
/// use lmrc_gitlab::{GitLabClient, error::Result};
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
///     let client = GitLabClient::new("https://gitlab.com", "your-token")?;
///
///     // List pipelines
///     let pipelines = client.pipelines("myproject").list().await?;
///
///     // Get specific pipeline
///     let pipeline = client.pipeline("myproject", 123).get().await?;
///
///     Ok(())
/// }
/// ```
pub struct GitLabClient {
    client: Gitlab,
}

impl GitLabClient {
    /// Creates a new GitLab client with the specified URL and personal access token.
    ///
    /// # Arguments
    ///
    /// * `url` - The GitLab instance URL (e.g., "<https://gitlab.com>")
    /// * `token` - A GitLab personal access token with appropriate permissions
    ///
    /// # Errors
    ///
    /// Returns [`GitLabError::Config`] if the URL is invalid or the token is malformed.
    /// Returns [`GitLabError::Authentication`] if the credentials are invalid.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use lmrc_gitlab::GitLabClient;
    ///
    /// let client = GitLabClient::new("https://gitlab.com", "glpat-xxxxxxxxxxxx")?;
    /// # Ok::<(), lmrc_gitlab::GitLabError>(())
    /// ```
    pub fn new(url: &str, token: &str) -> Result<Self> {
        if url.is_empty() {
            return Err(GitLabError::config("GitLab URL cannot be empty"));
        }

        if token.is_empty() {
            return Err(GitLabError::config("GitLab token cannot be empty"));
        }

        let client = Gitlab::new(url, token)
            .map_err(|e| GitLabError::config(format!("Failed to create GitLab client: {}", e)))?;

        Ok(Self { client })
    }

    /// Returns a reference to the underlying GitLab API client.
    ///
    /// This provides direct access to the `gitlab` crate's client for advanced use cases
    /// not covered by this library's API.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use lmrc_gitlab::GitLabClient;
    ///
    /// let client = GitLabClient::new("https://gitlab.com", "token")?;
    /// let raw_client = client.client();
    /// # Ok::<(), lmrc_gitlab::GitLabError>(())
    /// ```
    pub fn client(&self) -> &Gitlab {
        &self.client
    }

    /// Creates a builder for listing pipelines in a project.
    ///
    /// # Arguments
    ///
    /// * `project` - The project path (e.g., "myorg/myproject") or ID
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use lmrc_gitlab::{GitLabClient, models::PipelineStatus};
    ///
    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
    /// let client = GitLabClient::new("https://gitlab.com", "token")?;
    ///
    /// // List all pipelines
    /// let all = client.pipelines("myorg/myproject").list().await?;
    ///
    /// // List failed pipelines on main branch
    /// let failed = client.pipelines("myorg/myproject")
    ///     .status(PipelineStatus::Failed)
    ///     .ref_name("main")
    ///     .limit(10)
    ///     .list()
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn pipelines(&self, project: impl Into<String>) -> PipelineListBuilder<'_> {
        PipelineListBuilder::new(&self.client, project)
    }

    /// Creates a builder for operations on a specific pipeline.
    ///
    /// # Arguments
    ///
    /// * `project` - The project path (e.g., "myorg/myproject") or ID
    /// * `pipeline_id` - The pipeline ID
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use lmrc_gitlab::GitLabClient;
    ///
    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
    /// let client = GitLabClient::new("https://gitlab.com", "token")?;
    ///
    /// // Get pipeline details
    /// let pipeline = client.pipeline("myorg/myproject", 12345).get().await?;
    ///
    /// // Retry a failed pipeline
    /// client.pipeline("myorg/myproject", 12345).retry().await?;
    ///
    /// // Cancel a running pipeline
    /// client.pipeline("myorg/myproject", 12345).cancel().await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn pipeline(&self, project: impl Into<String>, pipeline_id: u64) -> PipelineBuilder<'_> {
        PipelineBuilder::new(&self.client, project, pipeline_id)
    }

    /// Creates a builder for listing jobs in a project or pipeline.
    ///
    /// # Arguments
    ///
    /// * `project` - The project path (e.g., "myorg/myproject") or ID
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use lmrc_gitlab::{GitLabClient, models::JobStatus};
    ///
    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
    /// let client = GitLabClient::new("https://gitlab.com", "token")?;
    ///
    /// // List jobs in a specific pipeline
    /// let jobs = client.jobs("myorg/myproject")
    ///     .pipeline(12345)
    ///     .list()
    ///     .await?;
    ///
    /// // List failed jobs
    /// let failed = client.jobs("myorg/myproject")
    ///     .pipeline(12345)
    ///     .status(JobStatus::Failed)
    ///     .list()
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn jobs(&self, project: impl Into<String>) -> JobListBuilder<'_> {
        JobListBuilder::new(&self.client, project)
    }

    /// Creates a builder for operations on a specific job.
    ///
    /// # Arguments
    ///
    /// * `project` - The project path (e.g., "myorg/myproject") or ID
    /// * `job_id` - The job ID
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use lmrc_gitlab::GitLabClient;
    ///
    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
    /// let client = GitLabClient::new("https://gitlab.com", "token")?;
    ///
    /// // Get job details
    /// let job = client.job("myorg/myproject", 67890).get().await?;
    ///
    /// // Retry a failed job
    /// client.job("myorg/myproject", 67890).retry().await?;
    ///
    /// // Get job logs
    /// let logs = client.job("myorg/myproject", 67890).logs().await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn job(&self, project: impl Into<String>, job_id: u64) -> JobBuilder<'_> {
        JobBuilder::new(&self.client, project, job_id)
    }

    /// Creates a builder for project-level operations.
    ///
    /// Use this for operations at the project scope, such as creating pipelines.
    ///
    /// # Arguments
    ///
    /// * `project` - The project path (e.g., "myorg/myproject") or ID
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use lmrc_gitlab::GitLabClient;
    ///
    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
    /// let client = GitLabClient::new("https://gitlab.com", "token")?;
    ///
    /// // Trigger a new pipeline
    /// let pipeline = client.project("myorg/myproject")
    ///     .create_pipeline()
    ///     .ref_name("main")
    ///     .variable("ENVIRONMENT", "production")
    ///     .trigger()
    ///     .await?;
    ///
    /// println!("Created pipeline #{}", pipeline.id);
    /// # Ok(())
    /// # }
    /// ```
    pub fn project(&self, project: impl Into<String>) -> ProjectBuilder<'_> {
        ProjectBuilder::new(&self.client, project)
    }

    /// Creates a builder for managing project CI/CD variables.
    ///
    /// Use this to create, update, list, or delete CI/CD variables for a project.
    ///
    /// # Arguments
    ///
    /// * `project` - The project path (e.g., "myorg/myproject") or ID
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use lmrc_gitlab::{GitLabClient, models::VariableOptions};
    ///
    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
    /// let client = GitLabClient::new("https://gitlab.com", "token")?;
    ///
    /// // Create a new variable
    /// let opts = VariableOptions::new()
    ///     .protected(true)
    ///     .masked(true);
    ///
    /// client.variables("myorg/myproject")
    ///     .create("API_KEY", "secret-value", opts)
    ///     .await?;
    ///
    /// // List all variables
    /// let vars = client.variables("myorg/myproject").list().await?;
    ///
    /// // Update or create (upsert)
    /// client.variables("myorg/myproject")
    ///     .set("DATABASE_URL", "postgres://...", VariableOptions::new())
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn variables(&self, project: impl Into<String>) -> VariableBuilder<'_> {
        VariableBuilder::new(&self.client, project)
    }
}
