//! Proto <-> types conversions
//!
//! Converts between protobuf types and IAM domain types.

use iam_types::{
    Condition as TypesCondition, ConditionExpr as TypesConditionExpr,
    Organization as TypesOrganization, Permission as TypesPermission,
    PolicyBinding as TypesBinding, Principal as TypesPrincipal,
    PrincipalKind as TypesPrincipalKind, PrincipalRef as TypesPrincipalRef,
    Project as TypesProject, Role as TypesRole, Scope as TypesScope,
};

use crate::proto::{
    self, condition_expr, scope, Condition, ConditionExpr, Organization, Permission, PolicyBinding,
    Principal, PrincipalKind, PrincipalRef, Project, Role, Scope,
};

// ============================================================================
// PrincipalKind conversions
// ============================================================================

/// Convert types PrincipalKind to proto enum value
pub fn principal_kind_to_proto(kind: &TypesPrincipalKind) -> i32 {
    match kind {
        TypesPrincipalKind::User => PrincipalKind::User as i32,
        TypesPrincipalKind::ServiceAccount => PrincipalKind::ServiceAccount as i32,
        TypesPrincipalKind::Group => PrincipalKind::Group as i32,
    }
}

/// Convert proto enum value to types PrincipalKind
pub fn proto_to_principal_kind(value: i32) -> Result<TypesPrincipalKind, &'static str> {
    match PrincipalKind::try_from(value) {
        Ok(PrincipalKind::User) => Ok(TypesPrincipalKind::User),
        Ok(PrincipalKind::ServiceAccount) => Ok(TypesPrincipalKind::ServiceAccount),
        Ok(PrincipalKind::Group) => Ok(TypesPrincipalKind::Group),
        _ => Err("invalid principal kind"),
    }
}

// ============================================================================
// PrincipalRef conversions
// ============================================================================

impl From<TypesPrincipalRef> for PrincipalRef {
    fn from(r: TypesPrincipalRef) -> Self {
        PrincipalRef {
            kind: principal_kind_to_proto(&r.kind),
            id: r.id,
        }
    }
}

/// Convert proto PrincipalRef to types PrincipalRef
pub fn proto_to_principal_ref(r: &PrincipalRef) -> Result<TypesPrincipalRef, &'static str> {
    let kind = proto_to_principal_kind(r.kind)?;
    Ok(TypesPrincipalRef::new(kind, &r.id))
}

// ============================================================================
// Principal conversions
// ============================================================================

impl From<TypesPrincipal> for Principal {
    fn from(p: TypesPrincipal) -> Self {
        Principal {
            id: p.id,
            kind: principal_kind_to_proto(&p.kind),
            name: p.name,
            org_id: p.org_id,
            project_id: p.project_id,
            email: p.email,
            oidc_sub: p.oidc_sub,
            node_id: p.node_id,
            metadata: p.metadata,
            created_at: p.created_at,
            updated_at: p.updated_at,
            enabled: p.enabled,
        }
    }
}

impl From<Principal> for TypesPrincipal {
    fn from(p: Principal) -> Self {
        TypesPrincipal {
            id: p.id,
            kind: proto_to_principal_kind(p.kind).unwrap_or(TypesPrincipalKind::User),
            name: p.name,
            org_id: p.org_id,
            project_id: p.project_id,
            email: p.email,
            oidc_sub: p.oidc_sub,
            node_id: p.node_id,
            metadata: p.metadata,
            created_at: p.created_at,
            updated_at: p.updated_at,
            enabled: p.enabled,
        }
    }
}

// ============================================================================
// Organization / Project conversions
// ============================================================================

impl From<TypesOrganization> for Organization {
    fn from(org: TypesOrganization) -> Self {
        Organization {
            id: org.id,
            name: org.name,
            description: org.description,
            metadata: org.metadata,
            created_at: org.created_at,
            updated_at: org.updated_at,
            enabled: org.enabled,
        }
    }
}

impl From<Organization> for TypesOrganization {
    fn from(org: Organization) -> Self {
        TypesOrganization {
            id: org.id,
            name: org.name,
            description: org.description,
            metadata: org.metadata,
            created_at: org.created_at,
            updated_at: org.updated_at,
            enabled: org.enabled,
        }
    }
}

impl From<TypesProject> for Project {
    fn from(project: TypesProject) -> Self {
        Project {
            id: project.id,
            org_id: project.org_id,
            name: project.name,
            description: project.description,
            metadata: project.metadata,
            created_at: project.created_at,
            updated_at: project.updated_at,
            enabled: project.enabled,
        }
    }
}

impl From<Project> for TypesProject {
    fn from(project: Project) -> Self {
        TypesProject {
            id: project.id,
            org_id: project.org_id,
            name: project.name,
            description: project.description,
            metadata: project.metadata,
            created_at: project.created_at,
            updated_at: project.updated_at,
            enabled: project.enabled,
        }
    }
}

// ============================================================================
// Scope conversions
// ============================================================================

impl From<TypesScope> for Scope {
    fn from(s: TypesScope) -> Self {
        Scope {
            scope: Some(match s {
                TypesScope::System => scope::Scope::System(true),
                TypesScope::Org { id } => scope::Scope::Org(proto::OrgScope { id }),
                TypesScope::Project { id, org_id } => {
                    scope::Scope::Project(proto::ProjectScope { id, org_id })
                }
                TypesScope::Resource {
                    id,
                    project_id,
                    org_id,
                } => scope::Scope::Resource(proto::ResourceScope {
                    id,
                    project_id,
                    org_id,
                }),
            }),
        }
    }
}

impl From<Scope> for TypesScope {
    fn from(s: Scope) -> Self {
        match s.scope {
            Some(scope::Scope::System(true)) => TypesScope::System,
            Some(scope::Scope::Org(org)) => TypesScope::org(org.id),
            Some(scope::Scope::Project(proj)) => TypesScope::project(proj.id, proj.org_id),
            Some(scope::Scope::Resource(res)) => {
                TypesScope::resource(res.id, res.project_id, res.org_id)
            }
            _ => TypesScope::System,
        }
    }
}

// ============================================================================
// Permission conversions
// ============================================================================

impl From<TypesPermission> for Permission {
    fn from(p: TypesPermission) -> Self {
        Permission {
            action: p.action,
            resource_pattern: p.resource_pattern,
            condition: p.condition.map(|c| c.into()),
        }
    }
}

impl From<Permission> for TypesPermission {
    fn from(p: Permission) -> Self {
        TypesPermission {
            action: p.action,
            resource_pattern: p.resource_pattern,
            condition: p.condition.map(|c| c.into()),
        }
    }
}

// ============================================================================
// Role conversions
// ============================================================================

impl From<TypesRole> for Role {
    fn from(r: TypesRole) -> Self {
        Role {
            name: r.name,
            display_name: r.display_name,
            description: r.description,
            scope: Some(r.scope.into()),
            permissions: r.permissions.into_iter().map(|p| p.into()).collect(),
            builtin: r.builtin,
            created_at: r.created_at,
            updated_at: r.updated_at,
        }
    }
}

impl From<Role> for TypesRole {
    fn from(r: Role) -> Self {
        TypesRole {
            name: r.name,
            display_name: r.display_name,
            description: r.description,
            scope: r
                .scope
                .unwrap_or(Scope {
                    scope: Some(scope::Scope::System(true)),
                })
                .into(),
            permissions: r.permissions.into_iter().map(Into::into).collect(),
            builtin: r.builtin,
            created_at: r.created_at,
            updated_at: r.updated_at,
        }
    }
}

// ============================================================================
// PolicyBinding conversions
// ============================================================================

impl From<TypesBinding> for PolicyBinding {
    fn from(b: TypesBinding) -> Self {
        PolicyBinding {
            id: b.id,
            principal: Some(b.principal_ref.into()),
            role: b.role_ref,
            scope: Some(b.scope.into()),
            condition: b.condition.map(|c| c.into()),
            created_at: b.created_at,
            updated_at: b.updated_at,
            created_by: b.created_by,
            expires_at: b.expires_at,
            enabled: b.enabled,
        }
    }
}

impl From<PolicyBinding> for TypesBinding {
    fn from(b: PolicyBinding) -> Self {
        TypesBinding {
            id: b.id,
            principal_ref: proto_to_principal_ref(&b.principal.unwrap_or_else(|| PrincipalRef {
                kind: PrincipalKind::User as i32,
                id: String::new(),
            }))
            .unwrap_or_else(|_| TypesPrincipalRef::user("")),
            role_ref: b.role,
            scope: b
                .scope
                .unwrap_or(Scope {
                    scope: Some(scope::Scope::System(true)),
                })
                .into(),
            condition: b.condition.map(|c| c.into()),
            created_at: b.created_at,
            updated_at: b.updated_at,
            created_by: b.created_by,
            expires_at: b.expires_at,
            enabled: b.enabled,
        }
    }
}

// ============================================================================
// Condition conversions
// ============================================================================

impl From<TypesCondition> for Condition {
    fn from(c: TypesCondition) -> Self {
        Condition {
            expression: Some(c.expression.into()),
        }
    }
}

impl From<Condition> for TypesCondition {
    fn from(c: Condition) -> Self {
        TypesCondition {
            expression: c
                .expression
                .map(|e| e.into())
                .unwrap_or(TypesConditionExpr::And(vec![])),
        }
    }
}

impl From<TypesConditionExpr> for ConditionExpr {
    fn from(e: TypesConditionExpr) -> Self {
        let expr = match e {
            TypesConditionExpr::StringEquals { key, value } => {
                condition_expr::Expr::StringEquals(proto::StringEqualsExpr { key, value })
            }
            TypesConditionExpr::StringNotEquals { key, value } => {
                condition_expr::Expr::StringNotEquals(proto::StringNotEqualsExpr { key, value })
            }
            TypesConditionExpr::StringLike { key, pattern } => {
                condition_expr::Expr::StringLike(proto::StringLikeExpr { key, pattern })
            }
            TypesConditionExpr::StringNotLike { key, pattern } => {
                condition_expr::Expr::StringNotLike(proto::StringNotLikeExpr { key, pattern })
            }
            TypesConditionExpr::NumericEquals { key, value } => {
                condition_expr::Expr::NumericEquals(proto::NumericEqualsExpr { key, value })
            }
            TypesConditionExpr::NumericLessThan { key, value } => {
                condition_expr::Expr::NumericLessThan(proto::NumericLessThanExpr { key, value })
            }
            TypesConditionExpr::NumericLessThanEquals { key, value } => {
                condition_expr::Expr::NumericLessThan(proto::NumericLessThanExpr { key, value })
            }
            TypesConditionExpr::NumericGreaterThan { key, value } => {
                condition_expr::Expr::NumericGreaterThan(proto::NumericGreaterThanExpr {
                    key,
                    value,
                })
            }
            TypesConditionExpr::NumericGreaterThanEquals { key, value } => {
                condition_expr::Expr::NumericGreaterThan(proto::NumericGreaterThanExpr {
                    key,
                    value,
                })
            }
            TypesConditionExpr::IpAddress { key, cidr } => {
                condition_expr::Expr::IpAddress(proto::IpAddressExpr { key, cidr })
            }
            TypesConditionExpr::NotIpAddress { key, cidr } => {
                condition_expr::Expr::NotIpAddress(proto::NotIpAddressExpr { key, cidr })
            }
            TypesConditionExpr::TimeBetween { start, end } => {
                condition_expr::Expr::TimeBetween(proto::TimeBetweenExpr { start, end })
            }
            TypesConditionExpr::Exists { key } => {
                condition_expr::Expr::Exists(proto::ExistsExpr { key })
            }
            TypesConditionExpr::StringEqualsAny { key, values } => {
                condition_expr::Expr::StringEqualsAny(proto::StringEqualsAnyExpr { key, values })
            }
            TypesConditionExpr::Bool { key, value } => {
                condition_expr::Expr::BoolExpr(proto::BoolExpr { key, value })
            }
            TypesConditionExpr::And(exprs) => condition_expr::Expr::AndExpr(proto::AndExpr {
                expressions: exprs.into_iter().map(|e| e.into()).collect(),
            }),
            TypesConditionExpr::Or(exprs) => condition_expr::Expr::OrExpr(proto::OrExpr {
                expressions: exprs.into_iter().map(|e| e.into()).collect(),
            }),
            TypesConditionExpr::Not(expr) => {
                condition_expr::Expr::NotExpr(Box::new(proto::NotExpr {
                    expression: Some(Box::new((*expr).into())),
                }))
            }
        };
        ConditionExpr { expr: Some(expr) }
    }
}

impl From<ConditionExpr> for TypesConditionExpr {
    fn from(e: ConditionExpr) -> Self {
        match e.expr {
            Some(condition_expr::Expr::StringEquals(e)) => TypesConditionExpr::StringEquals {
                key: e.key,
                value: e.value,
            },
            Some(condition_expr::Expr::StringNotEquals(e)) => TypesConditionExpr::StringNotEquals {
                key: e.key,
                value: e.value,
            },
            Some(condition_expr::Expr::StringLike(e)) => TypesConditionExpr::StringLike {
                key: e.key,
                pattern: e.pattern,
            },
            Some(condition_expr::Expr::StringNotLike(e)) => TypesConditionExpr::StringNotLike {
                key: e.key,
                pattern: e.pattern,
            },
            Some(condition_expr::Expr::NumericEquals(e)) => TypesConditionExpr::NumericEquals {
                key: e.key,
                value: e.value,
            },
            Some(condition_expr::Expr::NumericLessThan(e)) => TypesConditionExpr::NumericLessThan {
                key: e.key,
                value: e.value,
            },
            Some(condition_expr::Expr::NumericGreaterThan(e)) => {
                TypesConditionExpr::NumericGreaterThan {
                    key: e.key,
                    value: e.value,
                }
            }
            Some(condition_expr::Expr::IpAddress(e)) => TypesConditionExpr::IpAddress {
                key: e.key,
                cidr: e.cidr,
            },
            Some(condition_expr::Expr::NotIpAddress(e)) => TypesConditionExpr::NotIpAddress {
                key: e.key,
                cidr: e.cidr,
            },
            Some(condition_expr::Expr::TimeBetween(e)) => TypesConditionExpr::TimeBetween {
                start: e.start,
                end: e.end,
            },
            Some(condition_expr::Expr::Exists(e)) => TypesConditionExpr::Exists { key: e.key },
            Some(condition_expr::Expr::StringEqualsAny(e)) => TypesConditionExpr::StringEqualsAny {
                key: e.key,
                values: e.values,
            },
            Some(condition_expr::Expr::BoolExpr(e)) => TypesConditionExpr::Bool {
                key: e.key,
                value: e.value,
            },
            Some(condition_expr::Expr::AndExpr(e)) => {
                TypesConditionExpr::And(e.expressions.into_iter().map(|x| x.into()).collect())
            }
            Some(condition_expr::Expr::OrExpr(e)) => {
                TypesConditionExpr::Or(e.expressions.into_iter().map(|x| x.into()).collect())
            }
            Some(condition_expr::Expr::NotExpr(e)) => {
                let inner = e
                    .expression
                    .map(|x| (*x).into())
                    .unwrap_or(TypesConditionExpr::And(vec![]));
                TypesConditionExpr::Not(Box::new(inner))
            }
            None => TypesConditionExpr::And(vec![]),
        }
    }
}
