use std::sync::Arc;

use tracing::info;
use uuid::Uuid;
use validator::Validate;

use crate::error::{auth, http, ControlCenterError, Result};
use crate::models::{CreateRoleRequest, Role, RoleResponse, UpdateRoleRequest};
use crate::services::DatabaseService;

/// Role service for managing role operations
#[derive(Clone)]
pub struct RoleService {
    db: Arc<DatabaseService>,
}

impl RoleService {
    /// Create a new role service
    pub fn new(db: Arc<DatabaseService>) -> Self {
        Self { db }
    }

    /// Create a new role
    pub async fn create_role(&self, request: CreateRoleRequest) -> Result<RoleResponse> {
        // Validate request
        request
            .validate()
            .map_err(|e| ControlCenterError::Http(http::HttpError::Validation(e.to_string())))?;

        // Check if role already exists
        if self.find_by_name(&request.name).await?.is_some() {
            let msg = format!("Role with name '{}' already exists", request.name);
            return Err(ControlCenterError::Http(http::conflict(&msg)));
        }

        // Create role
        let role = Role::new(
            request.name,
            request.description,
            request.permissions,
            false, // User-created roles are not system roles
        );

        // Save to database
        let created_role: Role =
            self.db
                .db
                .create("roles")
                .content(role)
                .await?
                .ok_or_else(|| {
                    ControlCenterError::from(crate::error::database::DatabaseError::Database(
                        "Failed to create role".to_string(),
                    ))
                })?;

        info!("Created role: {}", created_role.name);

        Ok(RoleResponse::from(created_role))
    }

    /// Get role by ID
    pub async fn get_by_id(&self, role_id: Uuid) -> Result<Role> {
        let query = "SELECT * FROM roles WHERE role_id = $role_id";
        let mut result = self.db.db.query(query).bind(("role_id", role_id)).await?;

        let roles: Vec<Role> = result.take(0)?;
        roles.into_iter().next().ok_or_else(|| {
            ControlCenterError::Auth(auth::AuthError::RoleNotFound(role_id.to_string()))
        })
    }

    /// Get role response by ID (without sensitive data)
    pub async fn get_role_response_by_id(&self, role_id: Uuid) -> Result<RoleResponse> {
        let role = self.get_by_id(role_id).await?;
        Ok(RoleResponse::from(role))
    }

    /// Find role by name
    pub async fn find_by_name(&self, name: &str) -> Result<Option<Role>> {
        let query = "SELECT * FROM roles WHERE name = $name";
        let mut result = self
            .db
            .db
            .query(query)
            .bind(("name", name.to_string()))
            .await?;

        let roles: Vec<Role> = result.take(0)?;
        Ok(roles.into_iter().next())
    }

    /// Update role
    pub async fn update_role(
        &self,
        role_id: Uuid,
        request: UpdateRoleRequest,
    ) -> Result<RoleResponse> {
        // Validate request
        request
            .validate()
            .map_err(|e| ControlCenterError::Http(http::HttpError::Validation(e.to_string())))?;

        // Get existing role
        let mut role = self.get_by_id(role_id).await?;

        // Check if it's a system role
        if role.is_system {
            return Err(ControlCenterError::Http(http::HttpError::BadRequest(
                "Cannot modify system roles".to_string(),
            )));
        }

        // Check for conflicts if name is being changed
        if let Some(ref new_name) = request.name {
            if new_name != &role.name && self.find_by_name(new_name).await?.is_some() {
                let msg = format!("Role with name '{}' already exists", new_name);
                return Err(ControlCenterError::Http(http::conflict(&msg)));
            }
        }

        // Update role
        role.update(request);

        // Save to database
        let query = "UPDATE roles SET
            name = $name,
            description = $description,
            permissions = $permissions,
            updated_at = $updated_at
            WHERE role_id = $role_id";

        // Clone data before using in database operations to avoid borrow issues
        let name = role.name.clone();
        let description = role.description.clone();
        let permissions = role.permissions.clone();
        let updated_at = role.updated_at;

        self.db
            .db
            .query(query)
            .bind(("name", name))
            .bind(("description", description))
            .bind(("permissions", permissions))
            .bind(("updated_at", updated_at))
            .bind(("role_id", role_id))
            .await?;

        info!("Updated role: {}", role.name);

        Ok(RoleResponse::from(role))
    }

    /// Delete role
    pub async fn delete_role(&self, role_id: Uuid) -> Result<()> {
        // Get existing role
        let role = self.get_by_id(role_id).await?;

        // Check if it's a system role
        if role.is_system {
            return Err(ControlCenterError::Http(http::HttpError::BadRequest(
                "Cannot delete system roles".to_string(),
            )));
        }

        // Check if role is in use by any users
        let user_count = self.get_users_with_role_count(&role.name).await?;
        if user_count > 0 {
            let msg = format!(
                "Cannot delete role '{}' - it is assigned to {} users",
                role.name, user_count
            );
            return Err(ControlCenterError::Http(http::conflict(&msg)));
        }

        // Delete role
        let query = "DELETE roles WHERE role_id = $role_id";
        self.db.db.query(query).bind(("role_id", role_id)).await?;

        info!("Deleted role: {}", role.name);

        Ok(())
    }

    /// List all roles
    pub async fn list_roles(
        &self,
        include_system: Option<bool>,
        limit: Option<usize>,
        offset: Option<usize>,
    ) -> Result<Vec<RoleResponse>> {
        let mut query = "SELECT * FROM roles".to_string();

        if let Some(include_system) = include_system {
            if !include_system {
                query.push_str(" WHERE is_system = false");
            }
        }

        query.push_str(" ORDER BY created_at DESC");

        if let Some(limit) = limit {
            query.push_str(&format!(" LIMIT {}", limit));
        }

        if let Some(offset) = offset {
            query.push_str(&format!(" START {}", offset));
        }

        let mut result = self.db.db.query(&query).await?;
        let roles: Vec<Role> = result.take(0)?;

        Ok(roles.into_iter().map(RoleResponse::from).collect())
    }

    /// Add permission to role
    pub async fn add_permission(&self, role_id: Uuid, permission: String) -> Result<RoleResponse> {
        let mut role = self.get_by_id(role_id).await?;

        // Check if it's a system role
        if role.is_system {
            return Err(ControlCenterError::Http(http::HttpError::BadRequest(
                "Cannot modify system roles".to_string(),
            )));
        }

        // Add permission if not already present
        role.add_permission(permission.clone());

        // Save to database
        let query = "UPDATE roles SET permissions = $permissions, updated_at = $updated_at WHERE \
                     role_id = $role_id";
        let permissions = role.permissions.clone();
        let updated_at = role.updated_at;
        self.db
            .db
            .query(query)
            .bind(("permissions", permissions))
            .bind(("updated_at", updated_at))
            .bind(("role_id", role_id))
            .await?;

        info!("Added permission '{}' to role: {}", permission, role.name);

        Ok(RoleResponse::from(role))
    }

    /// Remove permission from role
    pub async fn remove_permission(
        &self,
        role_id: Uuid,
        permission: String,
    ) -> Result<RoleResponse> {
        let mut role = self.get_by_id(role_id).await?;

        // Check if it's a system role
        if role.is_system {
            return Err(ControlCenterError::Http(http::HttpError::BadRequest(
                "Cannot modify system roles".to_string(),
            )));
        }

        // Remove permission
        role.remove_permission(&permission);

        // Save to database
        let query = "UPDATE roles SET permissions = $permissions, updated_at = $updated_at WHERE \
                     role_id = $role_id";
        let permissions = role.permissions.clone();
        let updated_at = role.updated_at;
        self.db
            .db
            .query(query)
            .bind(("permissions", permissions))
            .bind(("updated_at", updated_at))
            .bind(("role_id", role_id))
            .await?;

        info!(
            "Removed permission '{}' from role: {}",
            permission, role.name
        );

        Ok(RoleResponse::from(role))
    }

    /// Get count of users with a specific role
    async fn get_users_with_role_count(&self, role_name: &str) -> Result<i64> {
        let query = "SELECT count() FROM users WHERE $role_name IN roles";
        let mut result = self
            .db
            .db
            .query(query)
            .bind(("role_name", role_name.to_string()))
            .await?;

        let counts: Vec<i64> = result.take(0)?;
        Ok(counts.into_iter().next().unwrap_or(0))
    }

    /// Get role count
    pub async fn get_role_count(&self, include_system: Option<bool>) -> Result<i64> {
        let mut query = "SELECT count() FROM roles".to_string();

        if let Some(include_system) = include_system {
            if !include_system {
                query.push_str(" WHERE is_system = false");
            }
        }

        let mut result = self.db.db.query(&query).await?;
        let counts: Vec<i64> = result.take(0)?;

        Ok(counts.into_iter().next().unwrap_or(0))
    }

    /// Get roles by names
    pub async fn get_roles_by_names(&self, role_names: &[String]) -> Result<Vec<Role>> {
        if role_names.is_empty() {
            return Ok(Vec::new());
        }

        let query = "SELECT * FROM roles WHERE name IN $role_names";
        let mut result = self
            .db
            .db
            .query(query)
            .bind(("role_names", role_names.to_vec()))
            .await?;

        let roles: Vec<Role> = result.take(0)?;
        Ok(roles)
    }

    /// Check if role has permission
    pub async fn has_permission(&self, role_name: &str, permission: &str) -> Result<bool> {
        if let Some(role) = self.find_by_name(role_name).await? {
            Ok(role.has_permission(permission))
        } else {
            Ok(false)
        }
    }
}

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

    #[test]
    fn test_create_role_request_validation() {
        // Valid request
        let valid_request = CreateRoleRequest {
            name: "test_role".to_string(),
            description: Some("Test role description".to_string()),
            permissions: Some(vec!["read".to_string(), "write".to_string()]),
        };
        assert!(valid_request.validate().is_ok());

        // Short name
        let short_name = CreateRoleRequest {
            name: "a".to_string(),
            description: None,
            permissions: None,
        };
        assert!(short_name.validate().is_err());

        // Long name
        let long_name = CreateRoleRequest {
            name: "a".repeat(51),
            description: None,
            permissions: None,
        };
        assert!(long_name.validate().is_err());

        // Long description
        let long_description = CreateRoleRequest {
            name: "test_role".to_string(),
            description: Some("a".repeat(201)),
            permissions: None,
        };
        assert!(long_description.validate().is_err());
    }

    #[test]
    fn test_update_role_request_validation() {
        // Valid request
        let valid_request = UpdateRoleRequest {
            name: Some("updated_role".to_string()),
            description: Some("Updated description".to_string()),
            permissions: Some(vec!["read".to_string()]),
        };
        assert!(valid_request.validate().is_ok());

        // Short name
        let short_name = UpdateRoleRequest {
            name: Some("a".to_string()),
            description: None,
            permissions: None,
        };
        assert!(short_name.validate().is_err());

        // Valid empty update
        let empty_update = UpdateRoleRequest {
            name: None,
            description: None,
            permissions: None,
        };
        assert!(empty_update.validate().is_ok());
    }
}
