// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

//! REST API resource paths.

use crate::common::{CatalogOptions, Options};

use super::rest_util::RESTUtil;

/// Resource paths for REST API endpoints.
#[derive(Clone)]
pub struct ResourcePaths {
    base_path: String,
}

impl ResourcePaths {
    const V1: &'static str = "v1";
    const DATABASES: &'static str = "databases";
    const TABLES: &'static str = "tables";
    const TABLE_DETAILS: &'static str = "table-details";
    const PARTITIONS: &'static str = "partitions";
    const VIEWS: &'static str = "views";
    const FUNCTIONS: &'static str = "functions";

    /// Create a new ResourcePaths with the given prefix.
    pub fn new(prefix: &str) -> Self {
        let base_path = if prefix.is_empty() {
            format!("/{}", Self::V1)
        } else {
            format!("/{}/{}", Self::V1, prefix.trim_matches('/'))
        };
        ResourcePaths { base_path }
    }

    /// Create ResourcePaths from catalog options.
    pub fn for_catalog_properties(options: &Options) -> Self {
        let prefix = options
            .get(CatalogOptions::PREFIX)
            .map(|s| s.as_str())
            .unwrap_or("");
        Self::new(prefix)
    }

    /// Get the base path.
    pub fn base_path(&self) -> &str {
        &self.base_path
    }

    /// Get the config endpoint path.
    pub fn config() -> String {
        format!("/{}/config", Self::V1)
    }

    /// Get the databases endpoint path.
    pub fn databases(&self) -> String {
        format!("{}/{}", self.base_path, Self::DATABASES)
    }

    /// Get a specific database endpoint path.
    pub fn database(&self, name: &str) -> String {
        format!(
            "{}/{}/{}",
            self.base_path,
            Self::DATABASES,
            RESTUtil::encode_string(name)
        )
    }

    /// Get the tables endpoint path.
    pub fn tables(&self, database_name: Option<&str>) -> String {
        if let Some(db_name) = database_name {
            format!(
                "{}/{}/{}/{}",
                self.base_path,
                Self::DATABASES,
                RESTUtil::encode_string(db_name),
                Self::TABLES
            )
        } else {
            format!("{}/{}", self.base_path, Self::TABLES)
        }
    }

    /// Get a specific table endpoint path.
    pub fn table(&self, database_name: &str, table_name: &str) -> String {
        format!(
            "{}/{}/{}/{}/{}",
            self.base_path,
            Self::DATABASES,
            RESTUtil::encode_string(database_name),
            Self::TABLES,
            RESTUtil::encode_string(table_name)
        )
    }

    /// Get the views endpoint path for a database.
    pub fn views(&self, database_name: &str) -> String {
        format!(
            "{}/{}/{}/{}",
            self.base_path,
            Self::DATABASES,
            RESTUtil::encode_string(database_name),
            Self::VIEWS
        )
    }

    /// Get a specific view endpoint path.
    pub fn view(&self, database_name: &str, view_name: &str) -> String {
        format!(
            "{}/{}",
            self.views(database_name),
            RESTUtil::encode_string(view_name)
        )
    }

    /// Get the functions endpoint path for a database.
    pub fn functions(&self, database_name: &str) -> String {
        format!(
            "{}/{}/{}/{}",
            self.base_path,
            Self::DATABASES,
            RESTUtil::encode_string(database_name),
            Self::FUNCTIONS
        )
    }

    /// Get a specific function endpoint path.
    pub fn function(&self, database_name: &str, function_name: &str) -> String {
        format!(
            "{}/{}",
            self.functions(database_name),
            RESTUtil::encode_string(function_name)
        )
    }

    /// Get the auth-table-query endpoint path (`.../tables/{table}/auth`).
    pub fn auth_table(&self, database_name: &str, table_name: &str) -> String {
        format!("{}/auth", self.table(database_name, table_name))
    }

    /// Get the table details endpoint path.
    pub fn table_details(&self, database_name: &str) -> String {
        format!(
            "{}/{}/{}/{}",
            self.base_path,
            Self::DATABASES,
            RESTUtil::encode_string(database_name),
            Self::TABLE_DETAILS
        )
    }

    /// Get the table token endpoint path.
    pub fn table_token(&self, database_name: &str, table_name: &str) -> String {
        format!(
            "{}/{}/{}/{}/{}/token",
            self.base_path,
            Self::DATABASES,
            RESTUtil::encode_string(database_name),
            Self::TABLES,
            RESTUtil::encode_string(table_name)
        )
    }

    /// Get the rename table endpoint path.
    pub fn rename_table(&self) -> String {
        format!("{}/{}/rename", self.base_path, Self::TABLES)
    }

    /// Get the commit table endpoint path.
    pub fn commit_table(&self, database_name: &str, table_name: &str) -> String {
        format!(
            "{}/{}/{}/{}/{}/commit",
            self.base_path,
            Self::DATABASES,
            RESTUtil::encode_string(database_name),
            Self::TABLES,
            RESTUtil::encode_string(table_name)
        )
    }

    /// Get the rollback endpoint path for a table.
    pub fn rollback(&self, database_name: &str, table_name: &str) -> String {
        format!(
            "{}/{}/{}/{}/{}/rollback",
            self.base_path,
            Self::DATABASES,
            RESTUtil::encode_string(database_name),
            Self::TABLES,
            RESTUtil::encode_string(table_name)
        )
    }

    /// Get the partitions endpoint path for a table.
    pub fn partitions(&self, database_name: &str, table_name: &str) -> String {
        format!(
            "{}/{}/{}/{}/{}/{}",
            self.base_path,
            Self::DATABASES,
            RESTUtil::encode_string(database_name),
            Self::TABLES,
            RESTUtil::encode_string(table_name),
            Self::PARTITIONS
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_resource_paths_basic() {
        let paths = ResourcePaths::new("");
        assert_eq!(paths.databases(), "/v1/databases");
        assert_eq!(paths.tables(None), "/v1/tables");
    }

    #[test]
    fn test_resource_paths_with_prefix() {
        let paths = ResourcePaths::new("my-catalog");
        assert_eq!(paths.databases(), "/v1/my-catalog/databases");
        assert_eq!(
            paths.database("test-db"),
            "/v1/my-catalog/databases/test-db"
        );
    }

    #[test]
    fn test_resource_paths_table() {
        let paths = ResourcePaths::new("");
        let table_path = paths.table("my-db", "my-table");
        assert!(table_path.contains("my-db"));
        assert!(table_path.contains("my-table"));
    }

    #[test]
    fn test_auth_table_path_encodes_names() {
        let paths = ResourcePaths::new("catalog");
        assert_eq!(
            paths.auth_table("analytics db", "user events"),
            "/v1/catalog/databases/analytics+db/tables/user+events/auth"
        );
    }

    #[test]
    fn test_config_path() {
        assert_eq!(ResourcePaths::config(), "/v1/config");
    }

    #[test]
    fn test_views_path_encodes_database() {
        let paths = ResourcePaths::new("catalog");
        assert_eq!(
            paths.views("analytics db"),
            "/v1/catalog/databases/analytics+db/views"
        );
    }

    #[test]
    fn test_view_path_encodes_object_name() {
        let paths = ResourcePaths::new("catalog");
        assert_eq!(
            paths.view("analytics", "active ids"),
            "/v1/catalog/databases/analytics/views/active+ids"
        );
    }

    #[test]
    fn test_functions_path_encodes_database() {
        let paths = ResourcePaths::new("catalog");
        assert_eq!(
            paths.functions("analytics db"),
            "/v1/catalog/databases/analytics+db/functions"
        );
    }

    #[test]
    fn test_function_path_encodes_object_name() {
        let paths = ResourcePaths::new("catalog");
        assert_eq!(
            paths.function("analytics", "rectangle area"),
            "/v1/catalog/databases/analytics/functions/rectangle+area"
        );
    }
}
