"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);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var SubscriptionsService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SubscriptionsService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const schedule_1 = require("@nestjs/schedule");
const subscription_plan_entity_1 = require("../database/entities/subscription-plan.entity");
const subscription_entity_1 = require("../database/entities/subscription.entity");
const business_entity_1 = require("../database/entities/business.entity");
const payment_entity_1 = require("../database/entities/payment.entity");
const transaction_entity_1 = require("../database/entities/transaction.entity");
const router_entity_1 = require("../database/entities/router.entity");
const location_entity_1 = require("../database/entities/location.entity");
const user_entity_1 = require("../database/entities/user.entity");
const customer_entity_1 = require("../database/entities/customer.entity");
let SubscriptionsService = SubscriptionsService_1 = class SubscriptionsService {
    constructor(planRepo, subscriptionRepo, businessRepo, paymentRepo, transactionRepo, routerRepo, locationRepo, userRepo, customerRepo) {
        this.planRepo = planRepo;
        this.subscriptionRepo = subscriptionRepo;
        this.businessRepo = businessRepo;
        this.paymentRepo = paymentRepo;
        this.transactionRepo = transactionRepo;
        this.routerRepo = routerRepo;
        this.locationRepo = locationRepo;
        this.userRepo = userRepo;
        this.customerRepo = customerRepo;
        this.logger = new common_1.Logger(SubscriptionsService_1.name);
    }
    async getPlans() {
        return this.planRepo.find({
            order: { sortOrder: 'ASC', priceKes: 'ASC' },
        });
    }
    async getPlan(id) {
        const plan = await this.planRepo.findOne({ where: { id } });
        if (!plan) {
            throw new common_1.NotFoundException(`Plan ${id} not found`);
        }
        return plan;
    }
    async createPlan(dto) {
        const existing = await this.planRepo.findOne({
            where: { slug: dto.slug },
        });
        if (existing) {
            throw new common_1.BadRequestException(`Plan with slug "${dto.slug}" already exists`);
        }
        const plan = this.planRepo.create({
            name: dto.name,
            slug: dto.slug,
            description: dto.description || null,
            priceKes: dto.priceKes,
            billingCycle: dto.billingCycle,
            maxRouters: dto.maxRouters,
            maxLocations: dto.maxLocations,
            maxStaff: dto.maxStaff,
            maxCustomers: dto.maxCustomers,
            includesVouchers: dto.includesVouchers,
            includesCustomBranding: dto.includesCustomBranding,
            includesApiAccess: dto.includesApiAccess,
            includesPrioritySupport: dto.includesPrioritySupport,
            includesWhiteLabel: dto.includesWhiteLabel,
            commissionRate: dto.commissionRate,
            isActive: dto.isActive,
        });
        const saved = await this.planRepo.save(plan);
        this.logger.log(`Plan created: ${saved.id}`);
        return saved;
    }
    async updatePlan(id, dto) {
        const plan = await this.getPlan(id);
        if (dto.slug && dto.slug !== plan.slug) {
            const existing = await this.planRepo.findOne({
                where: { slug: dto.slug },
            });
            if (existing) {
                throw new common_1.BadRequestException(`Plan with slug "${dto.slug}" already exists`);
            }
        }
        Object.assign(plan, dto);
        const updated = await this.planRepo.save(plan);
        this.logger.log(`Plan ${id} updated`);
        return updated;
    }
    async deletePlan(id) {
        const plan = await this.getPlan(id);
        const activeSubscriptions = await this.subscriptionRepo.count({
            where: {
                planId: id,
                status: subscription_entity_1.SubscriptionStatus.ACTIVE,
            },
        });
        if (activeSubscriptions > 0) {
            throw new common_1.BadRequestException(`Cannot delete plan with ${activeSubscriptions} active subscriptions`);
        }
        plan.isActive = false;
        await this.planRepo.save(plan);
        this.logger.log(`Plan ${id} deactivated`);
        return { success: true };
    }
    async assignSubscription(businessId, planId, trialDays) {
        const business = await this.businessRepo.findOne({ where: { id: businessId } });
        if (!business) {
            throw new common_1.NotFoundException(`Business ${businessId} not found`);
        }
        const plan = await this.getPlan(planId);
        const existingSubscription = await this.subscriptionRepo.findOne({
            where: { businessId, status: subscription_entity_1.SubscriptionStatus.ACTIVE },
        });
        if (existingSubscription) {
            existingSubscription.status = subscription_entity_1.SubscriptionStatus.CANCELLED;
            existingSubscription.cancelledAt = new Date();
            existingSubscription.cancelledReason = 'Changed plan';
            await this.subscriptionRepo.save(existingSubscription);
        }
        const now = new Date();
        let endDate = null;
        let trialEndDate = null;
        if (trialDays && trialDays > 0) {
            trialEndDate = new Date(now);
            trialEndDate.setDate(trialEndDate.getDate() + trialDays);
        }
        else {
            endDate = this.calculateEndDate(now, plan.billingCycle);
        }
        const subscription = this.subscriptionRepo.create({
            businessId,
            planId,
            status: trialEndDate ? subscription_entity_1.SubscriptionStatus.TRIAL : subscription_entity_1.SubscriptionStatus.ACTIVE,
            startDate: now,
            endDate,
            trialEndDate,
            nextBillingDate: endDate,
            priceKes: plan.priceKes,
            autoRenew: true,
        });
        const saved = await this.subscriptionRepo.save(subscription);
        business.subscriptionId = saved.id;
        business.subscriptionStartDate = now;
        business.maxRouters = plan.maxRouters;
        business.maxLocations = plan.maxLocations;
        business.maxStaff = plan.maxStaff;
        business.maxCustomers = plan.maxCustomers;
        await this.businessRepo.save(business);
        await this.transactionRepo.save(this.transactionRepo.create({
            businessId,
            type: transaction_entity_1.TransactionType.SUBSCRIPTION,
            amount: plan.priceKes,
            balanceBefore: 0,
            balanceAfter: 0,
            description: `Subscription to ${plan.name}`,
            referenceId: saved.id,
        }));
        this.logger.log(`Subscription assigned: ${saved.id} for business ${businessId}`);
        return saved;
    }
    async upgradeSubscription(businessId, newPlanId) {
        const current = await this.subscriptionRepo.findOne({
            where: { businessId, status: subscription_entity_1.SubscriptionStatus.ACTIVE },
            relations: ['plan'],
        });
        if (!current) {
            throw new common_1.NotFoundException('No active subscription found');
        }
        if (current.planId === newPlanId) {
            throw new common_1.BadRequestException('Already subscribed to this plan');
        }
        const newPlan = await this.getPlan(newPlanId);
        current.status = subscription_entity_1.SubscriptionStatus.CANCELLED;
        current.cancelledAt = new Date();
        current.cancelledReason = `Upgraded to ${newPlan.name}`;
        await this.subscriptionRepo.save(current);
        const now = new Date();
        const endDate = this.calculateEndDate(now, newPlan.billingCycle);
        const subscription = this.subscriptionRepo.create({
            businessId,
            planId: newPlanId,
            status: subscription_entity_1.SubscriptionStatus.ACTIVE,
            startDate: now,
            endDate,
            nextBillingDate: endDate,
            priceKes: newPlan.priceKes,
            autoRenew: current.autoRenew,
        });
        const saved = await this.subscriptionRepo.save(subscription);
        const business = await this.businessRepo.findOne({ where: { id: businessId } });
        if (business) {
            business.subscriptionId = saved.id;
            business.subscriptionStartDate = now;
            business.maxRouters = newPlan.maxRouters;
            business.maxLocations = newPlan.maxLocations;
            business.maxStaff = newPlan.maxStaff;
            business.maxCustomers = newPlan.maxCustomers;
            await this.businessRepo.save(business);
        }
        await this.transactionRepo.save(this.transactionRepo.create({
            businessId,
            type: transaction_entity_1.TransactionType.SUBSCRIPTION,
            amount: newPlan.priceKes,
            balanceBefore: 0,
            balanceAfter: 0,
            description: `Upgraded to ${newPlan.name}`,
            referenceId: saved.id,
        }));
        this.logger.log(`Subscription upgraded: ${businessId} from ${current.planId} to ${newPlanId}`);
        return saved;
    }
    async cancelSubscription(businessId, reason) {
        const subscription = await this.subscriptionRepo.findOne({
            where: { businessId, status: subscription_entity_1.SubscriptionStatus.ACTIVE },
        });
        if (!subscription) {
            throw new common_1.NotFoundException('No active subscription found');
        }
        subscription.status = subscription_entity_1.SubscriptionStatus.CANCELLED;
        subscription.cancelledAt = new Date();
        subscription.cancelledReason = reason;
        subscription.autoRenew = false;
        const saved = await this.subscriptionRepo.save(subscription);
        this.logger.log(`Subscription cancelled: ${saved.id} for business ${businessId}`);
        return saved;
    }
    async renewSubscription(businessId) {
        const subscription = await this.subscriptionRepo.findOne({
            where: { businessId },
            relations: ['plan'],
            order: { createdAt: 'DESC' },
        });
        if (!subscription) {
            throw new common_1.NotFoundException('No subscription found');
        }
        const plan = subscription.plan || (await this.getPlan(subscription.planId));
        const now = new Date();
        const endDate = this.calculateEndDate(now, plan.billingCycle);
        subscription.status = subscription_entity_1.SubscriptionStatus.ACTIVE;
        subscription.startDate = now;
        subscription.endDate = endDate;
        subscription.nextBillingDate = endDate;
        subscription.cancelledAt = null;
        subscription.cancelledReason = null;
        const saved = await this.subscriptionRepo.save(subscription);
        await this.transactionRepo.save(this.transactionRepo.create({
            businessId,
            type: transaction_entity_1.TransactionType.SUBSCRIPTION,
            amount: plan.priceKes,
            balanceBefore: 0,
            balanceAfter: 0,
            description: `Renewed ${plan.name}`,
            referenceId: saved.id,
        }));
        this.logger.log(`Subscription renewed: ${saved.id} for business ${businessId}`);
        return saved;
    }
    async getBusinessSubscription(businessId) {
        const subscription = await this.subscriptionRepo.findOne({
            where: { businessId },
            relations: ['plan'],
            order: { createdAt: 'DESC' },
        });
        if (!subscription) {
            throw new common_1.NotFoundException('No subscription found');
        }
        const plan = subscription.plan;
        const now = new Date();
        const daysRemaining = subscription.endDate
            ? Math.max(0, Math.ceil((subscription.endDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)))
            : 0;
        return {
            ...subscription,
            plan,
            daysRemaining,
            isExpired: subscription.status === subscription_entity_1.SubscriptionStatus.EXPIRED || (subscription.endDate && subscription.endDate < now),
        };
    }
    async checkSubscriptionStatus(businessId) {
        const business = await this.businessRepo.findOne({ where: { id: businessId } });
        if (!business) {
            throw new common_1.NotFoundException(`Business ${businessId} not found`);
        }
        const subscription = await this.subscriptionRepo.findOne({
            where: { businessId },
            relations: ['plan'],
            order: { createdAt: 'DESC' },
        });
        if (!subscription || (subscription.status !== subscription_entity_1.SubscriptionStatus.ACTIVE && subscription.status !== subscription_entity_1.SubscriptionStatus.TRIAL)) {
            return {
                active: false,
                plan: null,
                limits: {
                    routers: business.maxRouters,
                    locations: business.maxLocations,
                    staff: business.maxStaff,
                    customers: business.maxCustomers,
                },
                daysRemaining: 0,
            };
        }
        const now = new Date();
        const daysRemaining = subscription.endDate
            ? Math.max(0, Math.ceil((subscription.endDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)))
            : 0;
        return {
            active: true,
            plan: subscription.plan,
            limits: {
                routers: business.maxRouters,
                locations: business.maxLocations,
                staff: business.maxStaff,
                customers: business.maxCustomers,
            },
            daysRemaining,
        };
    }
    async enforceLimits(businessId, resource, currentCount) {
        const business = await this.businessRepo.findOne({ where: { id: businessId } });
        if (!business) {
            throw new common_1.NotFoundException(`Business ${businessId} not found`);
        }
        const limits = {
            routers: business.maxRouters,
            locations: business.maxLocations,
            staff: business.maxStaff,
            customers: business.maxCustomers,
        };
        const max = limits[resource];
        if (currentCount >= max) {
            throw new common_1.BadRequestException(`${resource.charAt(0).toUpperCase() + resource.slice(1)} limit reached (${currentCount}/${max}). Upgrade your plan to add more.`);
        }
    }
    async checkExpiredSubscriptions() {
        const now = new Date();
        const expired = await this.subscriptionRepo.find({
            where: { status: subscription_entity_1.SubscriptionStatus.ACTIVE },
        });
        const expiredSubscriptions = expired.filter((sub) => sub.endDate && sub.endDate < now);
        if (expiredSubscriptions.length > 0) {
            for (const sub of expiredSubscriptions) {
                sub.status = subscription_entity_1.SubscriptionStatus.EXPIRED;
                await this.subscriptionRepo.save(sub);
                this.logger.log(`Subscription ${sub.id} expired for business ${sub.businessId}`);
            }
        }
        const expiringTrials = await this.subscriptionRepo
            .createQueryBuilder('subscription')
            .where('subscription.status = :status', { status: subscription_entity_1.SubscriptionStatus.TRIAL })
            .andWhere('subscription.trialEndDate <= :tomorrow', {
            tomorrow: new Date(now.getTime() + 24 * 60 * 60 * 1000),
        })
            .andWhere('subscription.trialEndDate > :now', { now })
            .getMany();
        for (const sub of expiringTrials) {
            this.logger.log(`Trial expiring soon for business ${sub.businessId}: ${sub.trialEndDate}`);
        }
        if (expiredSubscriptions.length > 0 || expiringTrials.length > 0) {
            this.logger.log(`Subscription check: ${expiredSubscriptions.length} expired, ${expiringTrials.length} trials expiring soon`);
        }
    }
    calculateEndDate(startDate, billingCycle) {
        const endDate = new Date(startDate);
        switch (billingCycle) {
            case 'MONTHLY':
                endDate.setMonth(endDate.getMonth() + 1);
                break;
            case 'QUARTERLY':
                endDate.setMonth(endDate.getMonth() + 3);
                break;
            case 'ANNUAL':
                endDate.setFullYear(endDate.getFullYear() + 1);
                break;
            default:
                endDate.setMonth(endDate.getMonth() + 1);
        }
        return endDate;
    }
};
exports.SubscriptionsService = SubscriptionsService;
__decorate([
    (0, schedule_1.Cron)('0 0 * * *'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], SubscriptionsService.prototype, "checkExpiredSubscriptions", null);
exports.SubscriptionsService = SubscriptionsService = SubscriptionsService_1 = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, typeorm_1.InjectRepository)(subscription_plan_entity_1.SubscriptionPlan)),
    __param(1, (0, typeorm_1.InjectRepository)(subscription_entity_1.Subscription)),
    __param(2, (0, typeorm_1.InjectRepository)(business_entity_1.Business)),
    __param(3, (0, typeorm_1.InjectRepository)(payment_entity_1.Payment)),
    __param(4, (0, typeorm_1.InjectRepository)(transaction_entity_1.Transaction)),
    __param(5, (0, typeorm_1.InjectRepository)(router_entity_1.Router)),
    __param(6, (0, typeorm_1.InjectRepository)(location_entity_1.Location)),
    __param(7, (0, typeorm_1.InjectRepository)(user_entity_1.User)),
    __param(8, (0, typeorm_1.InjectRepository)(customer_entity_1.Customer)),
    __metadata("design:paramtypes", [typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository])
], SubscriptionsService);
