//! IAM Authorization gRPC service implementation

use std::net::IpAddr;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};

use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use tonic::{Request, Response, Status};

use iam_audit::{AuditEvent, AuditLogger};
use iam_authz::{AuthzContext, AuthzRequest as InternalAuthzRequest, PolicyEvaluator};
use iam_store::{BindingStore, GroupStore, OrgStore, PrincipalStore, ProjectStore, RoleStore};
use iam_types::{
    Error as TypesError, IamError, Organization, PolicyBinding, Principal,
    PrincipalKind as TypesPrincipalKind, PrincipalRef, Project, Resource, Role, Scope,
    StorageError,
};
use tracing::warn;
use uuid::Uuid;

use crate::proto::{
    self, iam_admin_server::IamAdmin, iam_authz_server::IamAuthz, AddGroupMemberRequest,
    AddGroupMemberResponse, AuthorizeRequest, AuthorizeResponse, BatchAuthorizeRequest,
    BatchAuthorizeResponse, CreateBindingRequest, CreateOrganizationRequest,
    CreatePrincipalRequest, CreateProjectRequest, CreateRoleRequest, DeleteBindingRequest,
    DeleteBindingResponse, DeleteOrganizationRequest, DeleteOrganizationResponse,
    DeletePrincipalRequest, DeletePrincipalResponse, DeleteProjectRequest, DeleteProjectResponse,
    DeleteRoleRequest, DeleteRoleResponse, GetBindingRequest, GetOrganizationRequest,
    GetPrincipalRequest, GetProjectRequest, GetRoleRequest, ListBindingsRequest,
    ListBindingsResponse, ListGroupMembersRequest, ListGroupMembersResponse,
    ListOrganizationsRequest, ListOrganizationsResponse, ListPrincipalGroupsRequest,
    ListPrincipalGroupsResponse, ListPrincipalsRequest, ListPrincipalsResponse,
    ListProjectsRequest, ListProjectsResponse, ListRolesRequest, ListRolesResponse, PrincipalKind,
    RemoveGroupMemberRequest, RemoveGroupMemberResponse, UpdateBindingRequest,
    UpdateOrganizationRequest, UpdatePrincipalRequest, UpdateProjectRequest, UpdateRoleRequest,
};

/// IAM Authorization service implementation
pub struct IamAuthzService {
    evaluator: Arc<PolicyEvaluator>,
    principal_store: Arc<PrincipalStore>,
    audit_logger: Option<Arc<AuditLogger>>,
}

impl IamAuthzService {
    /// Create a new authorization service without audit logging
    pub fn new(evaluator: Arc<PolicyEvaluator>, principal_store: Arc<PrincipalStore>) -> Self {
        Self {
            evaluator,
            principal_store,
            audit_logger: None,
        }
    }

    /// Create a new authorization service with audit logging
    pub fn with_audit(
        evaluator: Arc<PolicyEvaluator>,
        principal_store: Arc<PrincipalStore>,
        audit_logger: Arc<AuditLogger>,
    ) -> Self {
        Self {
            evaluator,
            principal_store,
            audit_logger: Some(audit_logger),
        }
    }

    /// Log an audit event (non-blocking)
    async fn log_event(&self, event: AuditEvent) {
        if let Some(logger) = &self.audit_logger {
            if let Err(e) = logger.log(event).await {
                warn!("Failed to log audit event: {}", e);
            }
        }
    }
}

#[tonic::async_trait]
impl IamAuthz for IamAuthzService {
    async fn authorize(
        &self,
        request: Request<AuthorizeRequest>,
    ) -> Result<Response<AuthorizeResponse>, Status> {
        let req = request.into_inner();

        // Get principal ref from request
        let principal_ref = req
            .principal
            .as_ref()
            .ok_or_else(|| Status::invalid_argument("principal is required"))?;

        let principal_kind = match PrincipalKind::try_from(principal_ref.kind) {
            Ok(PrincipalKind::User) => TypesPrincipalKind::User,
            Ok(PrincipalKind::ServiceAccount) => TypesPrincipalKind::ServiceAccount,
            Ok(PrincipalKind::Group) => TypesPrincipalKind::Group,
            _ => return Err(Status::invalid_argument("invalid principal kind")),
        };

        let types_principal_ref = PrincipalRef::new(principal_kind.clone(), &principal_ref.id);

        // Get principal from store
        let principal = self
            .principal_store
            .get(&types_principal_ref)
            .await
            .map_err(|e| Status::internal(format!("Failed to get principal: {}", e)))?
            .ok_or_else(|| Status::not_found("Principal not found"))?;

        // Build resource
        let resource_ref = req
            .resource
            .as_ref()
            .ok_or_else(|| Status::invalid_argument("resource is required"))?;

        let mut resource = Resource::new(
            &resource_ref.kind,
            &resource_ref.id,
            &resource_ref.org_id,
            &resource_ref.project_id,
        );

        if let Some(owner) = &resource_ref.owner_id {
            resource = resource.with_owner(owner);
        }
        if let Some(node) = &resource_ref.node_id {
            resource = resource.with_node(node);
        }
        if let Some(region) = &resource_ref.region {
            resource = resource.with_region(region);
        }
        for (k, v) in &resource_ref.tags {
            resource = resource.with_tag(k, v);
        }

        // Build context
        let mut context = AuthzContext::new();

        if let Some(ctx) = &req.context {
            if !ctx.source_ip.is_empty() {
                if let Ok(ip) = ctx.source_ip.parse::<IpAddr>() {
                    context = context.with_source_ip(ip);
                }
            }
            if ctx.timestamp > 0 {
                context = context.with_timestamp(ctx.timestamp);
            }
            if !ctx.http_method.is_empty() {
                context = context.with_http_method(&ctx.http_method);
            }
            if !ctx.request_path.is_empty() {
                context = context.with_request_path(&ctx.request_path);
            }
            for (k, v) in &ctx.metadata {
                context = context.with_metadata(k, v);
            }
        }

        // Build internal request
        let internal_req =
            InternalAuthzRequest::new(principal, &req.action, resource).with_context(context);

        // Evaluate
        let evaluation = self
            .evaluator
            .evaluate_with_match(&internal_req)
            .await
            .map_err(|e| Status::internal(format!("Authorization error: {}", e)))?;

        // Determine scope for audit logging
        let audit_scope = Scope::project(&resource_ref.project_id, &resource_ref.org_id);

        let response = match evaluation.decision {
            iam_authz::AuthzDecision::Allow => {
                // Log allowed event
                let event = AuditEvent::authz_allowed(
                    &types_principal_ref.id,
                    &req.action,
                    &resource_ref.kind,
                    &resource_ref.id,
                    audit_scope.clone(),
                );
                self.log_event(event).await;

                AuthorizeResponse {
                    allowed: true,
                    reason: String::new(),
                    matched_binding: evaluation.matched_binding.unwrap_or_default(),
                    matched_role: evaluation.matched_role.unwrap_or_default(),
                }
            }
            iam_authz::AuthzDecision::Deny { reason } => {
                // Log denied event
                let event = AuditEvent::authz_denied(
                    &types_principal_ref.id,
                    &req.action,
                    &resource_ref.kind,
                    &resource_ref.id,
                    audit_scope,
                    &reason,
                );
                self.log_event(event).await;

                AuthorizeResponse {
                    allowed: false,
                    reason,
                    matched_binding: String::new(),
                    matched_role: String::new(),
                }
            }
        };

        Ok(Response::new(response))
    }

    async fn batch_authorize(
        &self,
        request: Request<BatchAuthorizeRequest>,
    ) -> Result<Response<BatchAuthorizeResponse>, Status> {
        let req = request.into_inner();
        let mut responses = Vec::with_capacity(req.requests.len());

        for auth_req in req.requests {
            let result = self
                .authorize(Request::new(auth_req))
                .await
                .map(|r| r.into_inner())
                .unwrap_or_else(|e| AuthorizeResponse {
                    allowed: false,
                    reason: e.message().to_string(),
                    matched_binding: String::new(),
                    matched_role: String::new(),
                });
            responses.push(result);
        }

        Ok(Response::new(BatchAuthorizeResponse { responses }))
    }
}

/// IAM Admin service implementation
pub struct IamAdminService {
    principal_store: Arc<PrincipalStore>,
    role_store: Arc<RoleStore>,
    binding_store: Arc<BindingStore>,
    org_store: Arc<OrgStore>,
    project_store: Arc<ProjectStore>,
    group_store: Arc<GroupStore>,
    evaluator: Option<Arc<PolicyEvaluator>>,
}

impl IamAdminService {
    /// Create a new admin service
    pub fn new(
        principal_store: Arc<PrincipalStore>,
        role_store: Arc<RoleStore>,
        binding_store: Arc<BindingStore>,
        org_store: Arc<OrgStore>,
        project_store: Arc<ProjectStore>,
        group_store: Arc<GroupStore>,
    ) -> Self {
        Self {
            principal_store,
            role_store,
            binding_store,
            org_store,
            project_store,
            group_store,
            evaluator: None,
        }
    }

    /// Attach the active policy evaluator so admin mutations can invalidate cache.
    pub fn with_evaluator(mut self, evaluator: Arc<PolicyEvaluator>) -> Self {
        self.evaluator = Some(evaluator);
        self
    }

    fn invalidate_principal_bindings(&self, principal: &PrincipalRef) {
        if let Some(evaluator) = &self.evaluator {
            evaluator.invalidate_principal(principal);
        }
    }

    fn invalidate_role(&self, role_ref: &str) {
        if let Some(evaluator) = &self.evaluator {
            evaluator.invalidate_role(role_ref.strip_prefix("roles/").unwrap_or(role_ref));
        }
    }

    async fn ensure_tenant_registration(
        &self,
        org_id: &str,
        project_id: Option<&str>,
    ) -> Result<(), Status> {
        let now = now_ts();
        let mut org = Organization::new(org_id, org_id);
        org.created_at = now;
        org.updated_at = now;
        self.org_store
            .create_if_missing(&org)
            .await
            .map_err(map_error)?;

        if let Some(project_id) = project_id {
            let mut project = Project::new(project_id, org_id, project_id);
            project.created_at = now;
            project.updated_at = now;
            self.project_store
                .create_if_missing(&project)
                .await
                .map_err(map_error)?;
        }

        Ok(())
    }

    async fn ensure_scope_registration(&self, scope: &Scope) -> Result<(), Status> {
        match scope {
            Scope::System => Ok(()),
            Scope::Org { id } => self.ensure_tenant_registration(id, None).await,
            Scope::Project { id, org_id } => {
                self.ensure_tenant_registration(org_id, Some(id)).await
            }
            Scope::Resource {
                project_id, org_id, ..
            } => {
                self.ensure_tenant_registration(org_id, Some(project_id))
                    .await
            }
        }
    }

    async fn validate_binding_principal_scope(
        &self,
        principal: &Principal,
        scope: &Scope,
    ) -> Result<(), Status> {
        if principal.kind != TypesPrincipalKind::ServiceAccount {
            return Ok(());
        }

        let Some(principal_org) = principal.org_id.as_deref() else {
            return Ok(());
        };

        let Some(principal_project) = principal.project_id.as_deref() else {
            return Ok(());
        };

        match scope {
            Scope::System => Ok(()),
            Scope::Org { id } if id == principal_org => Ok(()),
            Scope::Project { id, org_id } if org_id == principal_org && id == principal_project => {
                Ok(())
            }
            Scope::Resource {
                project_id, org_id, ..
            } if org_id == principal_org && project_id == principal_project => Ok(()),
            _ => Err(Status::permission_denied(
                "service account bindings must stay within the service account tenant",
            )),
        }
    }
}

fn now_ts() -> u64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs()
}

fn map_error(err: TypesError) -> Status {
    match err {
        TypesError::Iam(IamError::PrincipalNotFound(msg))
        | TypesError::Iam(IamError::RoleNotFound(msg))
        | TypesError::Iam(IamError::BindingNotFound(msg))
        | TypesError::Iam(IamError::OrganizationNotFound(msg))
        | TypesError::Iam(IamError::ProjectNotFound(msg)) => Status::not_found(msg),
        TypesError::Iam(IamError::PrincipalAlreadyExists(msg))
        | TypesError::Iam(IamError::RoleAlreadyExists(msg))
        | TypesError::Iam(IamError::BindingAlreadyExists(msg))
        | TypesError::Iam(IamError::OrganizationAlreadyExists(msg))
        | TypesError::Iam(IamError::ProjectAlreadyExists(msg)) => Status::already_exists(msg),
        TypesError::Iam(IamError::CannotModifyBuiltinRole(msg)) => Status::failed_precondition(msg),
        TypesError::Storage(StorageError::CasConflict { expected, actual }) => Status::aborted(
            format!("CAS conflict (expected {}, actual {})", expected, actual),
        ),
        TypesError::Storage(StorageError::Connection(msg)) => Status::unavailable(msg),
        TypesError::Storage(StorageError::Backend(msg)) => Status::unavailable(msg),
        TypesError::Storage(StorageError::Timeout) => Status::deadline_exceeded("storage timeout"),
        other => Status::internal(other.to_string()),
    }
}

fn parse_principal_kind(value: i32) -> Result<TypesPrincipalKind, Status> {
    match PrincipalKind::try_from(value) {
        Ok(PrincipalKind::User) => Ok(TypesPrincipalKind::User),
        Ok(PrincipalKind::ServiceAccount) => Ok(TypesPrincipalKind::ServiceAccount),
        Ok(PrincipalKind::Group) => Ok(TypesPrincipalKind::Group),
        _ => Err(Status::invalid_argument("invalid principal kind")),
    }
}

fn parse_principal_ref(principal: proto::PrincipalRef) -> Result<PrincipalRef, Status> {
    Ok(PrincipalRef::new(
        parse_principal_kind(principal.kind)?,
        principal.id,
    ))
}

fn decode_page_token(page_token: &str) -> Result<usize, Status> {
    if page_token.trim().is_empty() {
        return Ok(0);
    }

    let bytes = URL_SAFE_NO_PAD
        .decode(page_token.as_bytes())
        .map_err(|_| Status::invalid_argument("invalid page_token"))?;
    let value =
        String::from_utf8(bytes).map_err(|_| Status::invalid_argument("invalid page_token"))?;
    value
        .parse::<usize>()
        .map_err(|_| Status::invalid_argument("invalid page_token"))
}

fn encode_page_token(offset: usize) -> String {
    URL_SAFE_NO_PAD.encode(offset.to_string())
}

fn paginate<T>(
    mut items: Vec<T>,
    page_size: i32,
    page_token: &str,
) -> Result<(Vec<T>, String), Status> {
    let start = decode_page_token(page_token)?;
    if start >= items.len() {
        return Ok((Vec::new(), String::new()));
    }

    let requested = if page_size <= 0 {
        items.len().saturating_sub(start)
    } else {
        page_size as usize
    };
    let end = (start + requested).min(items.len());
    let next_page_token = if end < items.len() {
        encode_page_token(end)
    } else {
        String::new()
    };
    let page = items.drain(start..end).collect();
    Ok((page, next_page_token))
}

#[tonic::async_trait]
impl IamAdmin for IamAdminService {
    async fn create_principal(
        &self,
        request: Request<CreatePrincipalRequest>,
    ) -> Result<Response<proto::Principal>, Status> {
        let req = request.into_inner();
        let kind = parse_principal_kind(req.kind)?;

        let mut principal = match kind {
            TypesPrincipalKind::User => {
                if req
                    .project_id
                    .as_deref()
                    .is_some_and(|value| !value.trim().is_empty())
                {
                    return Err(Status::invalid_argument(
                        "project_id is not valid for user principals",
                    ));
                }
                Principal::new_user(&req.id, &req.name)
            }
            TypesPrincipalKind::ServiceAccount => {
                let org_id = req
                    .org_id
                    .clone()
                    .filter(|value| !value.trim().is_empty())
                    .ok_or_else(|| {
                        Status::invalid_argument("org_id is required for service accounts")
                    })?;
                let project_id = req
                    .project_id
                    .clone()
                    .filter(|value| !value.trim().is_empty())
                    .ok_or_else(|| {
                        Status::invalid_argument("project_id is required for service accounts")
                    })?;
                self.ensure_tenant_registration(&org_id, Some(&project_id))
                    .await?;
                Principal::new_service_account(&req.id, &req.name, org_id, project_id)
            }
            TypesPrincipalKind::Group => {
                if req
                    .project_id
                    .as_deref()
                    .is_some_and(|value| !value.trim().is_empty())
                {
                    return Err(Status::invalid_argument(
                        "project_id is not valid for group principals",
                    ));
                }
                Principal::new_group(&req.id, &req.name)
            }
        };

        principal.org_id = req.org_id.clone();
        principal.project_id = req.project_id.clone();
        principal.email = req.email.clone();
        principal.metadata = req.metadata.clone();
        if let Some(org_id) = principal.org_id.as_deref() {
            self.ensure_tenant_registration(org_id, principal.project_id.as_deref())
                .await?;
        }
        let now = now_ts();
        principal.created_at = now;
        principal.updated_at = now;

        self.principal_store
            .create(&principal)
            .await
            .map_err(map_error)?;

        Ok(Response::new(proto::Principal::from(principal)))
    }

    async fn get_principal(
        &self,
        request: Request<GetPrincipalRequest>,
    ) -> Result<Response<proto::Principal>, Status> {
        let principal_ref = request
            .into_inner()
            .principal
            .ok_or_else(|| Status::invalid_argument("principal is required"))?;
        let principal_ref = parse_principal_ref(principal_ref)?;

        let principal = self
            .principal_store
            .get(&principal_ref)
            .await
            .map_err(map_error)?
            .ok_or_else(|| Status::not_found("principal not found"))?;

        Ok(Response::new(proto::Principal::from(principal)))
    }

    async fn update_principal(
        &self,
        request: Request<UpdatePrincipalRequest>,
    ) -> Result<Response<proto::Principal>, Status> {
        let req = request.into_inner();
        let principal_ref = req
            .principal
            .ok_or_else(|| Status::invalid_argument("principal is required"))?;
        let principal_ref = parse_principal_ref(principal_ref)?;

        let (mut principal, version) = self
            .principal_store
            .get_with_version(&principal_ref)
            .await
            .map_err(map_error)?
            .ok_or_else(|| Status::not_found("principal not found"))?;

        if let Some(name) = req.name {
            principal.name = name;
        }
        if let Some(email) = req.email {
            principal.email = Some(email);
        }
        if !req.metadata.is_empty() {
            principal.metadata = req.metadata;
        }
        if let Some(enabled) = req.enabled {
            principal.enabled = enabled;
        }
        principal.updated_at = now_ts();

        self.principal_store
            .update(&principal, version)
            .await
            .map_err(map_error)?;

        Ok(Response::new(proto::Principal::from(principal)))
    }

    async fn delete_principal(
        &self,
        request: Request<DeletePrincipalRequest>,
    ) -> Result<Response<DeletePrincipalResponse>, Status> {
        let principal_ref = request
            .into_inner()
            .principal
            .ok_or_else(|| Status::invalid_argument("principal is required"))?;
        let principal_ref = parse_principal_ref(principal_ref)?;

        let deleted = self
            .principal_store
            .delete(&principal_ref)
            .await
            .map_err(map_error)?;

        Ok(Response::new(DeletePrincipalResponse { deleted }))
    }

    async fn list_principals(
        &self,
        request: Request<ListPrincipalsRequest>,
    ) -> Result<Response<ListPrincipalsResponse>, Status> {
        let req = request.into_inner();
        let kind_filter = req.kind.map(parse_principal_kind).transpose()?;
        let org_filter = req.org_id.clone().filter(|value| !value.trim().is_empty());
        let project_filter = req
            .project_id
            .clone()
            .filter(|value| !value.trim().is_empty());

        let mut principals = if let (Some(org_id), Some(project_id)) =
            (org_filter.as_deref(), project_filter.as_deref())
        {
            self.principal_store
                .list_by_tenant(org_id, project_id)
                .await
                .map_err(map_error)?
        } else if let Some(project_id) = project_filter.as_deref() {
            self.principal_store
                .list_by_project(project_id)
                .await
                .map_err(map_error)?
        } else if let Some(org_id) = org_filter.as_deref() {
            self.principal_store
                .list_by_org(org_id)
                .await
                .map_err(map_error)?
        } else if let Some(kind) = kind_filter.as_ref() {
            self.principal_store
                .list_by_kind(kind)
                .await
                .map_err(map_error)?
        } else {
            let mut all = Vec::new();
            for kind in [
                TypesPrincipalKind::User,
                TypesPrincipalKind::ServiceAccount,
                TypesPrincipalKind::Group,
            ] {
                all.extend(
                    self.principal_store
                        .list_by_kind(&kind)
                        .await
                        .map_err(map_error)?,
                );
            }
            all
        };

        principals.retain(|principal| {
            kind_filter
                .as_ref()
                .is_none_or(|kind| principal.kind == *kind)
                && org_filter
                    .as_deref()
                    .is_none_or(|org_id| principal.org_id.as_deref() == Some(org_id))
                && project_filter
                    .as_deref()
                    .is_none_or(|project_id| principal.project_id.as_deref() == Some(project_id))
        });
        principals.sort_by(|left, right| {
            left.kind
                .to_string()
                .cmp(&right.kind.to_string())
                .then_with(|| left.id.cmp(&right.id))
        });

        let (principals, next_page_token) = paginate(principals, req.page_size, &req.page_token)?;
        let principals = principals.into_iter().map(proto::Principal::from).collect();

        Ok(Response::new(ListPrincipalsResponse {
            principals,
            next_page_token,
        }))
    }

    async fn create_organization(
        &self,
        request: Request<CreateOrganizationRequest>,
    ) -> Result<Response<proto::Organization>, Status> {
        let req = request.into_inner();
        let now = now_ts();
        let mut org = Organization::new(req.id, req.name);
        org.description = req.description;
        org.metadata = req.metadata;
        org.created_at = now;
        org.updated_at = now;
        self.org_store.create(&org).await.map_err(map_error)?;
        Ok(Response::new(org.into()))
    }

    async fn get_organization(
        &self,
        request: Request<GetOrganizationRequest>,
    ) -> Result<Response<proto::Organization>, Status> {
        let org = self
            .org_store
            .get(&request.into_inner().id)
            .await
            .map_err(map_error)?
            .ok_or_else(|| Status::not_found("organization not found"))?;
        Ok(Response::new(org.into()))
    }

    async fn update_organization(
        &self,
        request: Request<UpdateOrganizationRequest>,
    ) -> Result<Response<proto::Organization>, Status> {
        let req = request.into_inner();
        let (mut org, version) = self
            .org_store
            .get_with_version(&req.id)
            .await
            .map_err(map_error)?
            .ok_or_else(|| Status::not_found("organization not found"))?;
        if let Some(name) = req.name {
            org.name = name;
        }
        if let Some(description) = req.description {
            org.description = description;
        }
        if !req.metadata.is_empty() {
            org.metadata = req.metadata;
        }
        if let Some(enabled) = req.enabled {
            org.enabled = enabled;
        }
        org.updated_at = now_ts();
        self.org_store
            .update(&org, version)
            .await
            .map_err(map_error)?;
        Ok(Response::new(org.into()))
    }

    async fn delete_organization(
        &self,
        request: Request<DeleteOrganizationRequest>,
    ) -> Result<Response<DeleteOrganizationResponse>, Status> {
        let deleted = self
            .org_store
            .delete(&request.into_inner().id)
            .await
            .map_err(map_error)?;
        Ok(Response::new(DeleteOrganizationResponse { deleted }))
    }

    async fn list_organizations(
        &self,
        request: Request<ListOrganizationsRequest>,
    ) -> Result<Response<ListOrganizationsResponse>, Status> {
        let req = request.into_inner();
        let mut organizations = self.org_store.list().await.map_err(map_error)?;
        if !req.include_disabled {
            organizations.retain(|org| org.enabled);
        }
        organizations.sort_by(|left, right| left.id.cmp(&right.id));
        let (organizations, next_page_token) =
            paginate(organizations, req.page_size, &req.page_token)?;
        Ok(Response::new(ListOrganizationsResponse {
            organizations: organizations.into_iter().map(Into::into).collect(),
            next_page_token,
        }))
    }

    async fn create_project(
        &self,
        request: Request<CreateProjectRequest>,
    ) -> Result<Response<proto::Project>, Status> {
        let req = request.into_inner();
        self.ensure_tenant_registration(&req.org_id, None).await?;
        let now = now_ts();
        let mut project = Project::new(req.id, req.org_id, req.name);
        project.description = req.description;
        project.metadata = req.metadata;
        project.created_at = now;
        project.updated_at = now;
        self.project_store
            .create(&project)
            .await
            .map_err(map_error)?;
        Ok(Response::new(project.into()))
    }

    async fn get_project(
        &self,
        request: Request<GetProjectRequest>,
    ) -> Result<Response<proto::Project>, Status> {
        let req = request.into_inner();
        let project = self
            .project_store
            .get(&req.org_id, &req.id)
            .await
            .map_err(map_error)?
            .ok_or_else(|| Status::not_found("project not found"))?;
        Ok(Response::new(project.into()))
    }

    async fn update_project(
        &self,
        request: Request<UpdateProjectRequest>,
    ) -> Result<Response<proto::Project>, Status> {
        let req = request.into_inner();
        let (mut project, version) = self
            .project_store
            .get_with_version(&req.org_id, &req.id)
            .await
            .map_err(map_error)?
            .ok_or_else(|| Status::not_found("project not found"))?;
        if let Some(name) = req.name {
            project.name = name;
        }
        if let Some(description) = req.description {
            project.description = description;
        }
        if !req.metadata.is_empty() {
            project.metadata = req.metadata;
        }
        if let Some(enabled) = req.enabled {
            project.enabled = enabled;
        }
        project.updated_at = now_ts();
        self.project_store
            .update(&project, version)
            .await
            .map_err(map_error)?;
        Ok(Response::new(project.into()))
    }

    async fn delete_project(
        &self,
        request: Request<DeleteProjectRequest>,
    ) -> Result<Response<DeleteProjectResponse>, Status> {
        let req = request.into_inner();
        let deleted = self
            .project_store
            .delete(&req.org_id, &req.id)
            .await
            .map_err(map_error)?;
        Ok(Response::new(DeleteProjectResponse { deleted }))
    }

    async fn list_projects(
        &self,
        request: Request<ListProjectsRequest>,
    ) -> Result<Response<ListProjectsResponse>, Status> {
        let req = request.into_inner();
        let mut projects = if let Some(org_id) = req
            .org_id
            .as_deref()
            .filter(|value| !value.trim().is_empty())
        {
            self.project_store
                .list_by_org(org_id)
                .await
                .map_err(map_error)?
        } else {
            self.project_store.list().await.map_err(map_error)?
        };
        if !req.include_disabled {
            projects.retain(|project| project.enabled);
        }
        projects.sort_by(|left, right| {
            left.org_id
                .cmp(&right.org_id)
                .then_with(|| left.id.cmp(&right.id))
        });
        let (projects, next_page_token) = paginate(projects, req.page_size, &req.page_token)?;
        Ok(Response::new(ListProjectsResponse {
            projects: projects.into_iter().map(Into::into).collect(),
            next_page_token,
        }))
    }

    async fn create_role(
        &self,
        request: Request<CreateRoleRequest>,
    ) -> Result<Response<proto::Role>, Status> {
        let req = request.into_inner();

        let scope = req
            .scope
            .ok_or_else(|| Status::invalid_argument("scope is required"))?
            .into();

        let permissions = req.permissions.into_iter().map(Into::into).collect();

        let mut role = Role::new(&req.name, scope, permissions);
        role.display_name = req.display_name;
        role.description = req.description;
        let now = now_ts();
        role.created_at = now;
        role.updated_at = now;

        self.role_store.create(&role).await.map_err(map_error)?;
        self.invalidate_role(&role.to_ref());
        Ok(Response::new(proto::Role::from(role)))
    }

    async fn get_role(
        &self,
        request: Request<GetRoleRequest>,
    ) -> Result<Response<proto::Role>, Status> {
        let name = request.into_inner().name;
        let role = self
            .role_store
            .get(&name)
            .await
            .map_err(map_error)?
            .ok_or_else(|| Status::not_found("role not found"))?;
        Ok(Response::new(proto::Role::from(role)))
    }

    async fn update_role(
        &self,
        request: Request<UpdateRoleRequest>,
    ) -> Result<Response<proto::Role>, Status> {
        let req = request.into_inner();
        let (mut role, version) = self
            .role_store
            .get_with_version(&req.name)
            .await
            .map_err(map_error)?
            .ok_or_else(|| Status::not_found("role not found"))?;

        if let Some(name) = req.display_name {
            role.display_name = name;
        }
        if let Some(desc) = req.description {
            role.description = desc;
        }
        if !req.permissions.is_empty() {
            role.permissions = req.permissions.into_iter().map(Into::into).collect();
        }
        role.updated_at = now_ts();

        self.role_store
            .update(&role, version)
            .await
            .map_err(map_error)?;
        self.invalidate_role(&role.to_ref());

        Ok(Response::new(proto::Role::from(role)))
    }

    async fn delete_role(
        &self,
        request: Request<DeleteRoleRequest>,
    ) -> Result<Response<DeleteRoleResponse>, Status> {
        let role_name = request.into_inner().name;
        let deleted = self
            .role_store
            .delete(&role_name)
            .await
            .map_err(map_error)?;
        if deleted {
            self.invalidate_role(&role_name);
        }

        Ok(Response::new(DeleteRoleResponse { deleted }))
    }

    async fn list_roles(
        &self,
        request: Request<ListRolesRequest>,
    ) -> Result<Response<ListRolesResponse>, Status> {
        let req = request.into_inner();
        let mut roles = self.role_store.list().await.map_err(map_error)?;

        if let Some(scope) = req.scope.clone() {
            let scope: Scope = scope.into();
            roles.retain(|role| role.scope.applies_to(&scope));
        }

        if !req.include_builtin {
            roles.retain(|r| !r.builtin);
        }
        roles.sort_by(|left, right| left.name.cmp(&right.name));

        let (roles, next_page_token) = paginate(roles, req.page_size, &req.page_token)?;
        let roles = roles.into_iter().map(proto::Role::from).collect();
        Ok(Response::new(ListRolesResponse {
            roles,
            next_page_token,
        }))
    }

    async fn create_binding(
        &self,
        request: Request<CreateBindingRequest>,
    ) -> Result<Response<proto::PolicyBinding>, Status> {
        let req = request.into_inner();

        let principal_ref = req
            .principal
            .ok_or_else(|| Status::invalid_argument("principal is required"))?;
        let principal_ref = parse_principal_ref(principal_ref)?;

        let scope: Scope = req
            .scope
            .ok_or_else(|| Status::invalid_argument("scope is required"))?
            .into();
        self.ensure_scope_registration(&scope).await?;

        let principal = self
            .principal_store
            .get(&principal_ref)
            .await
            .map_err(map_error)?
            .ok_or_else(|| Status::not_found("principal not found"))?;

        self.validate_binding_principal_scope(&principal, &scope)
            .await?;

        let role = self
            .role_store
            .get_by_ref(&req.role)
            .await
            .map_err(map_error)?
            .ok_or_else(|| Status::not_found("role not found"))?;
        if !role.scope.applies_to(&scope) {
            return Err(Status::invalid_argument(
                "role scope is not applicable to the requested binding scope",
            ));
        }

        let mut binding =
            PolicyBinding::new(Uuid::new_v4().to_string(), principal_ref, req.role, scope);
        binding.condition = req.condition.map(Into::into);
        binding.expires_at = req.expires_at;
        binding.created_at = now_ts();
        binding.updated_at = binding.created_at;
        binding.created_by = "iam-admin".into();

        self.binding_store
            .create(&binding)
            .await
            .map_err(map_error)?;
        self.invalidate_principal_bindings(&binding.principal_ref);

        Ok(Response::new(proto::PolicyBinding::from(binding)))
    }

    async fn get_binding(
        &self,
        request: Request<GetBindingRequest>,
    ) -> Result<Response<proto::PolicyBinding>, Status> {
        let id = request.into_inner().id;
        let binding = self
            .binding_store
            .get_by_id(&id)
            .await
            .map_err(map_error)?
            .ok_or_else(|| Status::not_found("binding not found"))?;

        Ok(Response::new(proto::PolicyBinding::from(binding)))
    }

    async fn update_binding(
        &self,
        request: Request<UpdateBindingRequest>,
    ) -> Result<Response<proto::PolicyBinding>, Status> {
        let req = request.into_inner();

        let (mut binding, version) = self
            .binding_store
            .get_by_id_with_version(&req.id)
            .await
            .map_err(map_error)?
            .ok_or_else(|| Status::not_found("binding not found"))?;

        if let Some(condition) = req.condition {
            binding.condition = Some(condition.into());
        }
        if let Some(exp) = req.expires_at {
            binding.expires_at = Some(exp);
        }
        if let Some(enabled) = req.enabled {
            binding.enabled = enabled;
        }
        binding.updated_at = now_ts();

        self.binding_store
            .update(&binding, version)
            .await
            .map_err(map_error)?;
        self.invalidate_principal_bindings(&binding.principal_ref);

        Ok(Response::new(proto::PolicyBinding::from(binding)))
    }

    async fn delete_binding(
        &self,
        request: Request<DeleteBindingRequest>,
    ) -> Result<Response<DeleteBindingResponse>, Status> {
        let id = request.into_inner().id;
        let binding = self
            .binding_store
            .get_by_id(&id)
            .await
            .map_err(map_error)?
            .ok_or_else(|| Status::not_found("binding not found"))?;

        let deleted = self
            .binding_store
            .delete(&binding.scope, &binding.principal_ref, &binding.id)
            .await
            .map_err(map_error)?;
        if deleted {
            self.invalidate_principal_bindings(&binding.principal_ref);
        }

        Ok(Response::new(DeleteBindingResponse { deleted }))
    }

    async fn list_bindings(
        &self,
        request: Request<ListBindingsRequest>,
    ) -> Result<Response<ListBindingsResponse>, Status> {
        let req = request.into_inner();

        let mut bindings = if let Some(principal) = req.principal.clone() {
            let principal_ref = parse_principal_ref(principal)?;
            self.binding_store
                .list_by_principal(&principal_ref)
                .await
                .map_err(map_error)?
        } else if let Some(role) = req.role.clone() {
            self.binding_store
                .list_by_role(&role)
                .await
                .map_err(map_error)?
        } else if let Some(scope) = req.scope.clone() {
            let scope: Scope = scope.into();
            let mut filtered = self.binding_store.list_all().await.map_err(map_error)?;
            filtered.retain(|binding| binding.scope == scope);
            filtered
        } else {
            self.binding_store.list_all().await.map_err(map_error)?
        };

        if !req.include_disabled {
            bindings.retain(|b| b.enabled);
        }
        bindings.sort_by(|left, right| left.id.cmp(&right.id));

        let (bindings, next_page_token) = paginate(bindings, req.page_size, &req.page_token)?;
        let bindings = bindings
            .into_iter()
            .map(proto::PolicyBinding::from)
            .collect();

        Ok(Response::new(ListBindingsResponse {
            bindings,
            next_page_token,
        }))
    }

    async fn add_group_member(
        &self,
        request: Request<AddGroupMemberRequest>,
    ) -> Result<Response<AddGroupMemberResponse>, Status> {
        let req = request.into_inner();
        let member_ref = req
            .principal
            .ok_or_else(|| Status::invalid_argument("principal is required"))?;
        let member_ref = parse_principal_ref(member_ref)?;
        let group_ref = PrincipalRef::group(&req.group_id);

        let group = self
            .principal_store
            .get(&group_ref)
            .await
            .map_err(map_error)?
            .ok_or_else(|| Status::not_found("group not found"))?;
        if group.kind != TypesPrincipalKind::Group {
            return Err(Status::invalid_argument(
                "group_id must reference a group principal",
            ));
        }
        self.principal_store
            .get(&member_ref)
            .await
            .map_err(map_error)?
            .ok_or_else(|| Status::not_found("member principal not found"))?;

        let added = !self
            .group_store
            .is_member(&req.group_id, &member_ref)
            .await
            .map_err(map_error)?;
        self.group_store
            .add_member(&req.group_id, &member_ref)
            .await
            .map_err(map_error)?;
        self.invalidate_principal_bindings(&member_ref);

        Ok(Response::new(AddGroupMemberResponse { added }))
    }

    async fn remove_group_member(
        &self,
        request: Request<RemoveGroupMemberRequest>,
    ) -> Result<Response<RemoveGroupMemberResponse>, Status> {
        let req = request.into_inner();
        let member_ref = req
            .principal
            .ok_or_else(|| Status::invalid_argument("principal is required"))?;
        let member_ref = parse_principal_ref(member_ref)?;
        let removed = self
            .group_store
            .remove_member(&req.group_id, &member_ref)
            .await
            .map_err(map_error)?;
        if removed {
            self.invalidate_principal_bindings(&member_ref);
        }
        Ok(Response::new(RemoveGroupMemberResponse { removed }))
    }

    async fn list_group_members(
        &self,
        request: Request<ListGroupMembersRequest>,
    ) -> Result<Response<ListGroupMembersResponse>, Status> {
        let req = request.into_inner();
        let group_ref = PrincipalRef::group(&req.group_id);
        self.principal_store
            .get(&group_ref)
            .await
            .map_err(map_error)?
            .ok_or_else(|| Status::not_found("group not found"))?;

        let mut members = Vec::new();
        for member_ref in self
            .group_store
            .list_members(&req.group_id)
            .await
            .map_err(map_error)?
        {
            if let Some(principal) = self
                .principal_store
                .get(&member_ref)
                .await
                .map_err(map_error)?
            {
                members.push(principal);
            }
        }
        members.sort_by(|left, right| {
            left.kind
                .to_string()
                .cmp(&right.kind.to_string())
                .then_with(|| left.id.cmp(&right.id))
        });
        let (members, next_page_token) = paginate(members, req.page_size, &req.page_token)?;
        Ok(Response::new(ListGroupMembersResponse {
            members: members.into_iter().map(Into::into).collect(),
            next_page_token,
        }))
    }

    async fn list_principal_groups(
        &self,
        request: Request<ListPrincipalGroupsRequest>,
    ) -> Result<Response<ListPrincipalGroupsResponse>, Status> {
        let req = request.into_inner();
        let principal_ref = req
            .principal
            .ok_or_else(|| Status::invalid_argument("principal is required"))?;
        let principal_ref = parse_principal_ref(principal_ref)?;
        self.principal_store
            .get(&principal_ref)
            .await
            .map_err(map_error)?
            .ok_or_else(|| Status::not_found("principal not found"))?;

        let mut groups = Vec::new();
        for group_id in self
            .group_store
            .list_groups(&principal_ref)
            .await
            .map_err(map_error)?
        {
            if let Some(group) = self
                .principal_store
                .get(&PrincipalRef::group(group_id))
                .await
                .map_err(map_error)?
            {
                groups.push(group);
            }
        }
        groups.sort_by(|left, right| left.id.cmp(&right.id));
        let (groups, next_page_token) = paginate(groups, req.page_size, &req.page_token)?;
        Ok(Response::new(ListPrincipalGroupsResponse {
            groups: groups.into_iter().map(Into::into).collect(),
            next_page_token,
        }))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use iam_authz::PolicyCache;
    use iam_store::Backend;

    fn admin_service() -> (
        IamAdminService,
        Arc<PrincipalStore>,
        Arc<RoleStore>,
        Arc<BindingStore>,
    ) {
        let backend = Arc::new(Backend::memory());
        let principal_store = Arc::new(PrincipalStore::new(backend.clone()));
        let role_store = Arc::new(RoleStore::new(backend.clone()));
        let binding_store = Arc::new(BindingStore::new(backend.clone()));
        let org_store = Arc::new(OrgStore::new(backend.clone()));
        let project_store = Arc::new(ProjectStore::new(backend.clone()));
        let group_store = Arc::new(GroupStore::new(backend));
        (
            IamAdminService::new(
                principal_store.clone(),
                role_store.clone(),
                binding_store.clone(),
                org_store,
                project_store,
                group_store,
            ),
            principal_store,
            role_store,
            binding_store,
        )
    }

    fn test_stores() -> (Arc<PrincipalStore>, Arc<RoleStore>, Arc<BindingStore>) {
        let backend = Arc::new(Backend::memory());
        let principal_store = Arc::new(PrincipalStore::new(backend.clone()));
        let role_store = Arc::new(RoleStore::new(backend.clone()));
        let binding_store = Arc::new(BindingStore::new(backend));
        (principal_store, role_store, binding_store)
    }

    #[tokio::test]
    async fn test_authorize_no_principal() {
        let (principal_store, role_store, binding_store) = test_stores();
        let cache = Arc::new(PolicyCache::default_config());
        let evaluator = Arc::new(PolicyEvaluator::new(binding_store, role_store, cache));

        let service = IamAuthzService::new(evaluator, principal_store);

        let req = AuthorizeRequest {
            principal: None,
            action: "compute:instances:create".into(),
            resource: Some(proto::ResourceRef {
                kind: "instance".into(),
                id: "vm-1".into(),
                org_id: "org-1".into(),
                project_id: "proj-1".into(),
                owner_id: None,
                node_id: None,
                region: None,
                tags: Default::default(),
            }),
            context: None,
        };

        let result = service.authorize(Request::new(req)).await;
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn test_admin_crud_binding_flow() {
        let (service, principal_store, role_store, _binding_store) = admin_service();

        // Create principal
        let principal_req = CreatePrincipalRequest {
            id: "alice".into(),
            kind: PrincipalKind::User as i32,
            name: "Alice".into(),
            org_id: None,
            project_id: None,
            email: None,
            metadata: Default::default(),
        };
        service
            .create_principal(Request::new(principal_req))
            .await
            .unwrap();

        // Create role
        let role_req = CreateRoleRequest {
            name: "ProjectViewer".into(),
            display_name: "Viewer".into(),
            description: "read-only".into(),
            scope: Some(proto::Scope {
                scope: Some(proto::scope::Scope::Project(proto::ProjectScope {
                    id: "proj-1".into(),
                    org_id: "org-1".into(),
                })),
            }),
            permissions: vec![proto::Permission {
                action: "*:read".into(),
                resource_pattern: "project/${project}/*".into(),
                condition: None,
            }],
        };
        service.create_role(Request::new(role_req)).await.unwrap();

        // Create binding
        let binding_req = CreateBindingRequest {
            principal: Some(proto::PrincipalRef {
                kind: PrincipalKind::User as i32,
                id: "alice".into(),
            }),
            role: "roles/ProjectViewer".into(),
            scope: Some(proto::Scope {
                scope: Some(proto::scope::Scope::Project(proto::ProjectScope {
                    id: "proj-1".into(),
                    org_id: "org-1".into(),
                })),
            }),
            condition: None,
            expires_at: None,
        };
        let binding_resp = service
            .create_binding(Request::new(binding_req))
            .await
            .unwrap()
            .into_inner();
        assert_eq!(binding_resp.role, "roles/ProjectViewer");

        // List bindings filtered by principal
        let list_resp = service
            .list_bindings(Request::new(ListBindingsRequest {
                principal: Some(proto::PrincipalRef {
                    kind: PrincipalKind::User as i32,
                    id: "alice".into(),
                }),
                role: None,
                scope: None,
                include_disabled: false,
                page_size: 0,
                page_token: String::new(),
            }))
            .await
            .unwrap()
            .into_inner();
        assert_eq!(list_resp.bindings.len(), 1);

        // Delete binding
        let del_resp = service
            .delete_binding(Request::new(DeleteBindingRequest {
                id: binding_resp.id,
            }))
            .await
            .unwrap()
            .into_inner();
        assert!(del_resp.deleted);

        // Principal still exists
        assert!(principal_store
            .get(&PrincipalRef::user("alice"))
            .await
            .unwrap()
            .is_some());
        // Role still exists
        assert!(role_store.get("ProjectViewer").await.unwrap().is_some());
    }

    #[tokio::test]
    async fn test_admin_org_project_crud_flow() {
        let (service, _principal_store, _role_store, _binding_store) = admin_service();

        let organization = service
            .create_organization(Request::new(CreateOrganizationRequest {
                id: "org-1".into(),
                name: "Org 1".into(),
                description: "primary org".into(),
                metadata: Default::default(),
            }))
            .await
            .unwrap()
            .into_inner();
        assert_eq!(organization.id, "org-1");

        let project = service
            .create_project(Request::new(CreateProjectRequest {
                id: "proj-1".into(),
                org_id: "org-1".into(),
                name: "Project 1".into(),
                description: "tenant project".into(),
                metadata: Default::default(),
            }))
            .await
            .unwrap()
            .into_inner();
        assert_eq!(project.org_id, "org-1");

        let projects = service
            .list_projects(Request::new(ListProjectsRequest {
                org_id: Some("org-1".into()),
                include_disabled: false,
                page_size: 0,
                page_token: String::new(),
            }))
            .await
            .unwrap()
            .into_inner();
        assert_eq!(projects.projects.len(), 1);
        assert_eq!(projects.projects[0].id, "proj-1");
    }

    #[tokio::test]
    async fn test_binding_creation_invalidates_cached_deny() {
        let (principal_store, role_store, binding_store) = test_stores();
        role_store.init_builtin_roles().await.unwrap();

        let principal = Principal::new_service_account(
            "svc-lightningstor",
            "svc-lightningstor",
            "org-1",
            "proj-1",
        );
        principal_store.create(&principal).await.unwrap();

        let cache = Arc::new(PolicyCache::default_config());
        let evaluator = Arc::new(PolicyEvaluator::new(
            binding_store.clone(),
            role_store.clone(),
            cache,
        ));
        let authz_service = IamAuthzService::new(evaluator.clone(), principal_store.clone());
        let tenant_backend = Arc::new(Backend::memory());
        let admin_service = IamAdminService::new(
            principal_store,
            role_store,
            binding_store,
            Arc::new(OrgStore::new(tenant_backend.clone())),
            Arc::new(ProjectStore::new(tenant_backend.clone())),
            Arc::new(GroupStore::new(tenant_backend)),
        )
        .with_evaluator(evaluator);

        let authorize_request = || AuthorizeRequest {
            principal: Some(proto::PrincipalRef {
                kind: PrincipalKind::ServiceAccount as i32,
                id: "svc-lightningstor".into(),
            }),
            action: "storage:buckets:read".into(),
            resource: Some(proto::ResourceRef {
                kind: "bucket".into(),
                id: "bucket-1".into(),
                org_id: "org-1".into(),
                project_id: "proj-1".into(),
                owner_id: None,
                node_id: None,
                region: None,
                tags: Default::default(),
            }),
            context: None,
        };

        let initial = authz_service
            .authorize(Request::new(authorize_request()))
            .await
            .unwrap()
            .into_inner();
        assert!(!initial.allowed);

        admin_service
            .create_binding(Request::new(CreateBindingRequest {
                principal: Some(proto::PrincipalRef {
                    kind: PrincipalKind::ServiceAccount as i32,
                    id: "svc-lightningstor".into(),
                }),
                role: "roles/ProjectAdmin".into(),
                scope: Some(proto::Scope {
                    scope: Some(proto::scope::Scope::Project(proto::ProjectScope {
                        id: "proj-1".into(),
                        org_id: "org-1".into(),
                    })),
                }),
                condition: None,
                expires_at: None,
            }))
            .await
            .unwrap();

        let allowed = authz_service
            .authorize(Request::new(authorize_request()))
            .await
            .unwrap()
            .into_inner();
        assert!(allowed.allowed);
    }
}
