// Copyright 2024 RustFS Team
//
// Licensed 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.

use crate::KeystoneClient;
use rustfs_policy::policy::Policy;
use std::collections::HashMap;
use std::sync::Arc;
use tracing::{debug, info};

/// Maps Keystone identities to RustFS concepts
pub struct KeystoneIdentityMapper {
    #[allow(
        dead_code,
        reason = "keeps the Keystone client alive for the mapper's lifetime; the mapping paths do not call through it yet (backlog#1823)"
    )]
    client: Arc<KeystoneClient>,
    role_policy_map: HashMap<String, String>,
    enable_tenant_prefix: bool,
}

impl KeystoneIdentityMapper {
    /// Create new identity mapper
    pub fn new(client: Arc<KeystoneClient>, enable_tenant_prefix: bool) -> Self {
        let mut role_policy_map = HashMap::new();

        // Default Keystone role mappings
        role_policy_map.insert("admin".to_string(), "AdminPolicy".to_string());
        role_policy_map.insert("Admin".to_string(), "AdminPolicy".to_string());
        role_policy_map.insert("Member".to_string(), "ReadWritePolicy".to_string());
        role_policy_map.insert("_member_".to_string(), "ReadOnlyPolicy".to_string());
        role_policy_map.insert("ResellerAdmin".to_string(), "AdminPolicy".to_string());
        role_policy_map.insert("SwiftOperator".to_string(), "ReadWritePolicy".to_string());
        role_policy_map.insert("objectstore:admin".to_string(), "AdminPolicy".to_string());
        role_policy_map.insert("objectstore:creator".to_string(), "ReadWritePolicy".to_string());

        Self {
            client,
            role_policy_map,
            enable_tenant_prefix,
        }
    }

    /// Add custom role-to-policy mapping
    pub fn add_role_mapping(&mut self, keystone_role: String, rustfs_policy: String) {
        info!("Adding role mapping: {} -> {}", keystone_role, rustfs_policy);
        self.role_policy_map.insert(keystone_role, rustfs_policy);
    }

    /// Add multiple role mappings
    pub fn add_role_mappings(&mut self, mappings: Vec<(String, String)>) {
        for (role, policy) in mappings {
            self.add_role_mapping(role, policy);
        }
    }

    /// Map Keystone roles to RustFS policy names
    pub fn map_roles_to_policies(&self, roles: &[String]) -> Vec<String> {
        let policies: Vec<String> = roles
            .iter()
            .filter_map(|role| self.role_policy_map.get(role).cloned())
            .collect();

        debug!("Mapped roles {:?} to policies {:?}", roles, policies);
        policies
    }

    /// Generate tenant-prefixed bucket name
    /// Format: <project_id>:<bucket_name>
    pub fn apply_tenant_prefix(&self, bucket: &str, project_id: Option<&str>) -> String {
        if !self.enable_tenant_prefix {
            return bucket.to_string();
        }

        if let Some(proj_id) = project_id {
            let prefixed = format!("{}:{}", proj_id, bucket);
            debug!("Applied tenant prefix: {} -> {}", bucket, prefixed);
            prefixed
        } else {
            bucket.to_string()
        }
    }

    /// Remove tenant prefix from bucket name
    pub fn remove_tenant_prefix(&self, prefixed_bucket: &str, project_id: Option<&str>) -> String {
        if !self.enable_tenant_prefix {
            return prefixed_bucket.to_string();
        }

        if let Some(proj_id) = project_id {
            let prefix = format!("{}:", proj_id);
            if prefixed_bucket.starts_with(&prefix) {
                let unprefixed = prefixed_bucket[prefix.len()..].to_string();
                debug!("Removed tenant prefix: {} -> {}", prefixed_bucket, unprefixed);
                return unprefixed;
            }
        }

        prefixed_bucket.to_string()
    }

    /// Check if bucket belongs to project
    pub fn is_project_bucket(&self, bucket: &str, project_id: Option<&str>) -> bool {
        if !self.enable_tenant_prefix {
            return true; // No multi-tenancy, all buckets accessible
        }

        if let Some(proj_id) = project_id {
            let prefix = format!("{}:", proj_id);
            bucket.starts_with(&prefix)
        } else {
            !bucket.contains(':') // No project ID, only unprefixed buckets
        }
    }

    /// Extract project ID from prefixed bucket name
    pub fn extract_project_id(&self, bucket: &str) -> Option<String> {
        if !self.enable_tenant_prefix {
            return None;
        }

        bucket.find(':').map(|pos| bucket[..pos].to_string())
    }

    /// Create default policies for Keystone roles
    pub fn create_default_policies(&self) -> HashMap<String, Policy> {
        let mut policies = HashMap::new();

        // Admin policy - full access
        let admin_json = r#"{
            "Version": "2012-10-17",
            "ID": "AdminPolicy",
            "Statement": [{
                "Sid": "AdminFullAccess",
                "Effect": "Allow",
                "Action": ["s3:*"],
                "Resource": ["arn:aws:s3:::*"]
            }]
        }"#;
        if let Ok(policy) = serde_json::from_str::<Policy>(admin_json) {
            policies.insert("AdminPolicy".to_string(), policy);
        }

        // ReadWrite policy - read/write access
        let readwrite_json = r#"{
            "Version": "2012-10-17",
            "ID": "ReadWritePolicy",
            "Statement": [{
                "Sid": "ReadWriteAccess",
                "Effect": "Allow",
                "Action": [
                    "s3:GetObject",
                    "s3:PutObject",
                    "s3:DeleteObject",
                    "s3:ListBucket",
                    "s3:GetBucketLocation",
                    "s3:ListBucketMultipartUploads",
                    "s3:ListMultipartUploadParts",
                    "s3:AbortMultipartUpload"
                ],
                "Resource": ["arn:aws:s3:::*"]
            }]
        }"#;
        if let Ok(policy) = serde_json::from_str::<Policy>(readwrite_json) {
            policies.insert("ReadWritePolicy".to_string(), policy);
        }

        // ReadOnly policy - read-only access
        let readonly_json = r#"{
            "Version": "2012-10-17",
            "ID": "ReadOnlyPolicy",
            "Statement": [{
                "Sid": "ReadOnlyAccess",
                "Effect": "Allow",
                "Action": [
                    "s3:GetObject",
                    "s3:ListBucket",
                    "s3:GetBucketLocation"
                ],
                "Resource": ["arn:aws:s3:::*"]
            }]
        }"#;
        if let Ok(policy) = serde_json::from_str::<Policy>(readonly_json) {
            policies.insert("ReadOnlyPolicy".to_string(), policy);
        }

        policies
    }

    /// Check if user has permission based on Keystone roles
    pub fn has_permission(&self, roles: &[String], action: &str, _resource: &str) -> bool {
        // Admin always has access
        if roles.iter().any(|r| r.eq_ignore_ascii_case("admin") || r == "ResellerAdmin") {
            return true;
        }

        // Check role-based permissions
        for role in roles {
            if let Some(policy_name) = self.role_policy_map.get(role) {
                match policy_name.as_str() {
                    "AdminPolicy" => return true,
                    "ReadWritePolicy" => {
                        if action.starts_with("s3:Get")
                            || action.starts_with("s3:Put")
                            || action.starts_with("s3:Delete")
                            || action.starts_with("s3:List")
                        {
                            return true;
                        }
                    }
                    "ReadOnlyPolicy" => {
                        if action.starts_with("s3:Get") || action.starts_with("s3:List") {
                            return true;
                        }
                    }
                    _ => continue,
                }
            }
        }

        false
    }

    /// Check if tenant prefixing is enabled
    pub fn is_tenant_prefix_enabled(&self) -> bool {
        self.enable_tenant_prefix
    }
}

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

    fn create_mapper() -> KeystoneIdentityMapper {
        let client = KeystoneClient::new(
            "http://localhost:5000".to_string(),
            KeystoneVersion::V3,
            None,
            None,
            None,
            "Default".to_string(),
            true,
            std::time::Duration::from_secs(30),
        );
        KeystoneIdentityMapper::new(Arc::new(client), true)
    }

    #[test]
    fn test_tenant_prefix() {
        let mapper = create_mapper();

        let prefixed = mapper.apply_tenant_prefix("mybucket", Some("proj123"));
        assert_eq!(prefixed, "proj123:mybucket");

        let unprefixed = mapper.remove_tenant_prefix("proj123:mybucket", Some("proj123"));
        assert_eq!(unprefixed, "mybucket");

        // No project ID
        let no_prefix = mapper.apply_tenant_prefix("mybucket", None);
        assert_eq!(no_prefix, "mybucket");
    }

    #[test]
    fn test_is_project_bucket() {
        let mapper = create_mapper();

        assert!(mapper.is_project_bucket("proj123:mybucket", Some("proj123")));
        assert!(!mapper.is_project_bucket("proj456:mybucket", Some("proj123")));
        assert!(!mapper.is_project_bucket("mybucket", Some("proj123")));
    }

    #[test]
    fn test_extract_project_id() {
        let mapper = create_mapper();

        assert_eq!(mapper.extract_project_id("proj123:mybucket"), Some("proj123".to_string()));
        assert_eq!(mapper.extract_project_id("mybucket"), None);
    }

    #[test]
    fn test_role_mapping() {
        let mapper = create_mapper();

        let roles = vec!["Member".to_string(), "admin".to_string()];
        let policies = mapper.map_roles_to_policies(&roles);

        assert!(policies.contains(&"ReadWritePolicy".to_string()));
        assert!(policies.contains(&"AdminPolicy".to_string()));
    }

    #[test]
    fn test_has_permission() {
        let mapper = create_mapper();

        // Admin has all permissions
        assert!(mapper.has_permission(&["admin".to_string()], "s3:DeleteBucket", ""));

        // Member has read/write permissions
        assert!(mapper.has_permission(&["Member".to_string()], "s3:PutObject", ""));
        assert!(mapper.has_permission(&["Member".to_string()], "s3:GetObject", ""));

        // _member_ has read-only permissions
        assert!(mapper.has_permission(&["_member_".to_string()], "s3:GetObject", ""));
        assert!(!mapper.has_permission(&["_member_".to_string()], "s3:PutObject", ""));
    }

    #[test]
    fn test_add_role_mapping() {
        let mut mapper = create_mapper();

        mapper.add_role_mapping("CustomRole".to_string(), "CustomPolicy".to_string());

        let policies = mapper.map_roles_to_policies(&["CustomRole".to_string()]);
        assert_eq!(policies, vec!["CustomPolicy".to_string()]);
    }
}
