"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FeatureAccessService = void 0;
const common_1 = require("@nestjs/common");
const client_1 = require("@prisma/client");
const audit_service_1 = require("../audit/audit.service");
const estate_scope_1 = require("../common/estate-scope");
const prisma_service_1 = require("../database/prisma.service");
const feature_catalog_1 = require("./feature-catalog");
let FeatureAccessService = class FeatureAccessService {
    prisma;
    audit;
    constructor(prisma, audit) {
        this.prisma = prisma;
        this.audit = audit;
    }
    listCatalog() {
        return feature_catalog_1.FEATURE_CATALOG;
    }
    async getMyFeatures(user) {
        return this.getRoleSettings(user.role, user.estateId);
    }
    async getRoleSettings(role, estateId) {
        this.assertRole(role);
        const normalizedEstateId = estateId ?? null;
        const features = feature_catalog_1.FEATURE_KEYS;
        const [globalSettings, estateSettings, planFeatures] = await Promise.all([
            this.prisma.globalRoleFeatureSetting.findMany({
                where: { role, feature: { in: features } }
            }),
            normalizedEstateId
                ? this.prisma.estateRoleFeatureSetting.findMany({
                    where: { estateId: normalizedEstateId, role, feature: { in: features } }
                })
                : Promise.resolve([]),
            normalizedEstateId ? this.getEstatePlanFeatureSet(normalizedEstateId) : Promise.resolve(null)
        ]);
        const globalByFeature = new Map(globalSettings.map((item) => [item.feature, item]));
        const estateByFeature = new Map(estateSettings.map((item) => [item.feature, item]));
        const planGatedRole = this.isPlanGatedRole(role);
        return feature_catalog_1.FEATURE_CATALOG.map((definition) => {
            const defaultEnabled = definition.defaultRoles.includes(role);
            const global = globalByFeature.get(definition.key);
            const estate = estateByFeature.get(definition.key);
            const globalEnabled = global?.enabled ?? defaultEnabled;
            const roleEnabled = estate?.enabled ?? globalEnabled;
            const planEnabled = !planGatedRole || !definition.estateConfigurable || !planFeatures ? true : planFeatures.has(definition.key);
            const effectiveEnabled = roleEnabled && planEnabled;
            return {
                ...definition,
                role,
                estateId: normalizedEstateId,
                defaultEnabled,
                globalEnabled,
                estateEnabled: estate?.enabled ?? null,
                planEnabled,
                effectiveEnabled,
                source: !planEnabled ? 'PLAN' : estate ? 'ESTATE' : global ? 'GLOBAL' : 'DEFAULT'
            };
        });
    }
    async getManageableRoleSettings(role, requestedEstateId, actor) {
        const estateId = this.resolveManagementScope(actor, role, requestedEstateId);
        return this.getRoleSettings(role, estateId);
    }
    async isFeatureEnabled(user, feature) {
        if (user.role === client_1.UserRole.SUPER_ADMIN) {
            return true;
        }
        const normalizedFeature = this.assertFeature(feature);
        const settings = await this.getRoleSettings(user.role, user.estateId);
        return Boolean(settings.find((item) => item.key === normalizedFeature)?.effectiveEnabled);
    }
    async updateRoleSettings(role, dto, actor) {
        this.assertRole(role);
        const estateId = this.resolveManagementScope(actor, role, dto.estateId);
        const settings = dto.settings.map((item) => ({
            feature: this.assertFeature(item.feature),
            enabled: item.enabled
        }));
        await Promise.all(settings.map((item) => this.assertCanSetFeature(actor, role, item.feature, item.enabled, estateId)));
        if (estateId) {
            await this.upsertEstateSettings(estateId, role, settings, actor.id);
        }
        else {
            await this.upsertGlobalSettings(role, settings, actor.id);
        }
        await this.audit.record({
            estateId,
            actorType: 'USER',
            actorId: actor.id,
            action: 'ROLE_FEATURE_SETTINGS_UPDATED',
            metadata: {
                role,
                scope: estateId ? 'ESTATE' : 'GLOBAL',
                features: settings.map((item) => item.feature)
            }
        });
        return this.getRoleSettings(role, estateId);
    }
    async getPlanSettings(code) {
        const normalizedCode = code.trim().toUpperCase();
        const plan = await this.prisma.subscriptionPlan.findUnique({
            where: { code: normalizedCode },
            include: { features: true }
        });
        if (!plan) {
            throw new common_1.BadRequestException(`Unknown plan: ${normalizedCode}`);
        }
        const enabledFeatures = new Set(plan.features.map((item) => item.feature));
        return {
            code: plan.code,
            name: plan.name,
            features: feature_catalog_1.FEATURE_CATALOG.map((definition) => ({
                ...definition,
                planCode: plan.code,
                enabled: enabledFeatures.has(definition.key)
            }))
        };
    }
    async updatePlanSettings(code, dto, actor) {
        if (actor.role !== client_1.UserRole.SUPER_ADMIN) {
            throw new common_1.ForbiddenException('Only super admins can manage plan features');
        }
        const normalizedCode = code.trim().toUpperCase();
        const plan = await this.prisma.subscriptionPlan.findUnique({ where: { code: normalizedCode } });
        if (!plan) {
            throw new common_1.BadRequestException(`Unknown plan: ${normalizedCode}`);
        }
        const settings = dto.settings.map((item) => ({
            feature: this.assertFeature(item.feature),
            enabled: item.enabled
        }));
        await this.prisma.$transaction(async (tx) => {
            const enabled = settings.filter((item) => item.enabled);
            if (enabled.length) {
                await tx.subscriptionPlanFeature.createMany({
                    data: enabled.map((item) => ({
                        planId: plan.id,
                        feature: item.feature
                    })),
                    skipDuplicates: true
                });
            }
            const disabled = settings.filter((item) => !item.enabled).map((item) => item.feature);
            if (disabled.length) {
                await tx.subscriptionPlanFeature.deleteMany({
                    where: { planId: plan.id, feature: { in: disabled } }
                });
            }
        });
        await this.audit.record({
            estateId: null,
            actorType: 'USER',
            actorId: actor.id,
            action: 'PLAN_FEATURE_SETTINGS_UPDATED',
            metadata: {
                planCode: normalizedCode,
                features: settings
            }
        });
        return this.getPlanSettings(normalizedCode);
    }
    async upsertGlobalSettings(role, settings, updatedById) {
        await this.prisma.$transaction(settings.map((item) => this.prisma.globalRoleFeatureSetting.upsert({
            where: { role_feature: { role, feature: item.feature } },
            create: {
                role,
                feature: item.feature,
                enabled: item.enabled,
                updatedById
            },
            update: {
                enabled: item.enabled,
                updatedById
            }
        })));
    }
    async upsertEstateSettings(estateId, role, settings, updatedById) {
        await this.prisma.$transaction(settings.map((item) => this.prisma.estateRoleFeatureSetting.upsert({
            where: { estateId_role_feature: { estateId, role, feature: item.feature } },
            create: {
                estateId,
                role,
                feature: item.feature,
                enabled: item.enabled,
                updatedById
            },
            update: {
                enabled: item.enabled,
                updatedById
            }
        })));
    }
    resolveManagementScope(actor, role, requestedEstateId) {
        if (actor.role === client_1.UserRole.SUPER_ADMIN) {
            return requestedEstateId ?? null;
        }
        if (actor.role !== client_1.UserRole.ESTATE_ADMIN) {
            throw new common_1.ForbiddenException('Only super admins and estate admins can manage feature settings');
        }
        if (role !== client_1.UserRole.RESIDENT && role !== client_1.UserRole.GUARD) {
            throw new common_1.ForbiddenException('Estate admins can only manage resident and guard feature settings');
        }
        const estateId = (0, estate_scope_1.resolveEstateScope)(actor, requestedEstateId);
        if (!estateId) {
            throw new common_1.ForbiddenException('Estate-scoped feature settings require an estate');
        }
        return estateId;
    }
    assertRole(role) {
        if (!Object.values(client_1.UserRole).includes(role)) {
            throw new common_1.BadRequestException(`Unknown role: ${role}`);
        }
    }
    assertFeature(feature) {
        if (!feature_catalog_1.FEATURE_KEYS.includes(feature)) {
            throw new common_1.BadRequestException(`Unknown feature: ${feature}`);
        }
        return feature;
    }
    async assertCanSetFeature(actor, role, feature, enabled, estateId) {
        if (actor.role === client_1.UserRole.SUPER_ADMIN) {
            return;
        }
        const definition = feature_catalog_1.FEATURE_CATALOG.find((item) => item.key === feature);
        if (!definition?.estateConfigurable || !definition.defaultRoles.includes(role)) {
            throw new common_1.ForbiddenException('Estate admins can only toggle supported resident or guard estate features');
        }
        if (enabled && estateId && !(await this.isFeatureInEstatePlan(estateId, feature))) {
            throw new common_1.ForbiddenException('This feature is not available on the estate subscription plan');
        }
    }
    isPlanGatedRole(role) {
        return role !== client_1.UserRole.SUPER_ADMIN && role !== client_1.UserRole.SALES_ADMIN;
    }
    async getEstatePlanFeatureSet(estateId) {
        const estate = await this.prisma.estate.findUnique({
            where: { id: estateId },
            select: {
                subscriptionPlan: {
                    select: {
                        features: { select: { feature: true } }
                    }
                }
            }
        });
        if (!estate?.subscriptionPlan) {
            return null;
        }
        return new Set(estate.subscriptionPlan.features.map((item) => item.feature));
    }
    async isFeatureInEstatePlan(estateId, feature) {
        if (!feature_catalog_1.PLAN_SCOPED_FEATURE_KEYS.includes(feature)) {
            return true;
        }
        const planFeatures = await this.getEstatePlanFeatureSet(estateId);
        return planFeatures ? planFeatures.has(feature) : true;
    }
};
exports.FeatureAccessService = FeatureAccessService;
exports.FeatureAccessService = FeatureAccessService = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [prisma_service_1.PrismaService,
        audit_service_1.AuditService])
], FeatureAccessService);
//# sourceMappingURL=feature-access.service.js.map