/**
 * Tenant Subscription Service
 * Manages tenant subscriptions for SaaS billing
 *
 * @module BillingUsage
 */

import { Repository, FindOptionsWhere, LessThan, MoreThan, Between } from 'typeorm';
import { AppDataSource } from '../../../shared/database/typeorm.config';
import {
  TenantSubscription,
  BillingCycle,
  SubscriptionStatus,
} from '../entities/tenant-subscription.entity';
import { SubscriptionPlan } from '../entities/subscription-plan.entity';

export interface ServiceContext {
  tenantId: string;
  userId?: string;
}

export interface CreateTenantSubscriptionDto {
  planId: string;
  billingCycle?: BillingCycle;
  currentPeriodStart: Date;
  currentPeriodEnd: Date;
  status?: SubscriptionStatus;
  trialStart?: Date;
  trialEnd?: Date;
  billingEmail?: string;
  billingName?: string;
  billingAddress?: Record<string, any>;
  taxId?: string;
  paymentMethodId?: string;
  paymentProvider?: string;
  stripeCustomerId?: string;
  stripeSubscriptionId?: string;
  currentPrice: number;
  discountPercent?: number;
  discountReason?: string;
  contractedUsers?: number;
  contractedBranches?: number;
  autoRenew?: boolean;
}

export interface UpdateTenantSubscriptionDto {
  planId?: string;
  billingCycle?: BillingCycle;
  currentPeriodStart?: Date;
  currentPeriodEnd?: Date;
  status?: SubscriptionStatus;
  billingEmail?: string;
  billingName?: string;
  billingAddress?: Record<string, any>;
  taxId?: string;
  paymentMethodId?: string;
  paymentProvider?: string;
  stripeCustomerId?: string;
  stripeSubscriptionId?: string;
  currentPrice?: number;
  discountPercent?: number;
  discountReason?: string;
  contractedUsers?: number;
  contractedBranches?: number;
  autoRenew?: boolean;
  cancelAtPeriodEnd?: boolean;
  cancellationReason?: string;
}

export interface SubscriptionFilters {
  status?: SubscriptionStatus;
  planId?: string;
  billingCycle?: BillingCycle;
  expiringBefore?: Date;
  expiringAfter?: Date;
}

export class TenantSubscriptionService {
  private repository: Repository<TenantSubscription>;
  private planRepository: Repository<SubscriptionPlan>;

  constructor() {
    this.repository = AppDataSource.getRepository(TenantSubscription);
    this.planRepository = AppDataSource.getRepository(SubscriptionPlan);
  }

  async findByTenantId(tenantId: string): Promise<TenantSubscription | null> {
    return this.repository.findOne({
      where: { tenantId },
      relations: ['plan'],
    });
  }

  async findAll(filters: SubscriptionFilters = {}): Promise<TenantSubscription[]> {
    const where: FindOptionsWhere<TenantSubscription> = {};

    if (filters.status) {
      where.status = filters.status;
    }

    if (filters.planId) {
      where.planId = filters.planId;
    }

    if (filters.billingCycle) {
      where.billingCycle = filters.billingCycle;
    }

    if (filters.expiringBefore) {
      where.currentPeriodEnd = LessThan(filters.expiringBefore);
    }

    if (filters.expiringAfter) {
      where.currentPeriodEnd = MoreThan(filters.expiringAfter);
    }

    return this.repository.find({
      where,
      relations: ['plan'],
      order: { currentPeriodEnd: 'ASC' },
    });
  }

  async create(ctx: ServiceContext, data: CreateTenantSubscriptionDto): Promise<TenantSubscription> {
    const subscription = this.repository.create({
      ...data,
      tenantId: ctx.tenantId,
    });
    return this.repository.save(subscription);
  }

  async update(
    ctx: ServiceContext,
    data: UpdateTenantSubscriptionDto
  ): Promise<TenantSubscription | null> {
    const subscription = await this.findByTenantId(ctx.tenantId);
    if (!subscription) {
      return null;
    }

    Object.assign(subscription, data);
    return this.repository.save(subscription);
  }

  async changePlan(
    ctx: ServiceContext,
    newPlanId: string,
    effectiveDate?: Date
  ): Promise<TenantSubscription | null> {
    const subscription = await this.findByTenantId(ctx.tenantId);
    if (!subscription) {
      return null;
    }

    const newPlan = await this.planRepository.findOne({ where: { id: newPlanId } });
    if (!newPlan) {
      throw new Error('Plan not found');
    }

    // Calculate new price based on billing cycle
    const newPrice =
      subscription.billingCycle === 'annual' && newPlan.baseAnnualPrice
        ? newPlan.baseAnnualPrice
        : newPlan.baseMonthlyPrice;

    subscription.planId = newPlanId;
    subscription.currentPrice = newPrice * (1 - (subscription.discountPercent || 0) / 100);

    if (effectiveDate && effectiveDate > new Date()) {
      // Schedule plan change for future
      // This would typically be stored in a separate table
    }

    return this.repository.save(subscription);
  }

  async cancel(
    ctx: ServiceContext,
    reason: string,
    cancelImmediately = false
  ): Promise<TenantSubscription | null> {
    const subscription = await this.findByTenantId(ctx.tenantId);
    if (!subscription) {
      return null;
    }

    if (cancelImmediately) {
      subscription.status = 'cancelled';
      subscription.cancelledAt = new Date();
    } else {
      subscription.cancelAtPeriodEnd = true;
    }

    subscription.cancellationReason = reason;
    return this.repository.save(subscription);
  }

  async reactivate(ctx: ServiceContext): Promise<TenantSubscription | null> {
    const subscription = await this.findByTenantId(ctx.tenantId);
    if (!subscription) {
      return null;
    }

    if (subscription.status === 'cancelled') {
      // Need to create new subscription period
      subscription.status = 'active';
      subscription.currentPeriodStart = new Date();
      subscription.currentPeriodEnd = this.calculatePeriodEnd(
        new Date(),
        subscription.billingCycle
      );
    }

    subscription.cancelAtPeriodEnd = false;
    subscription.cancellationReason = null as any;

    return this.repository.save(subscription);
  }

  async startTrial(
    ctx: ServiceContext,
    planId: string,
    trialDays: number
  ): Promise<TenantSubscription> {
    const plan = await this.planRepository.findOne({ where: { id: planId } });
    if (!plan) {
      throw new Error('Plan not found');
    }

    const now = new Date();
    const trialEnd = new Date(now.getTime() + trialDays * 24 * 60 * 60 * 1000);

    const subscription = this.repository.create({
      tenantId: ctx.tenantId,
      planId,
      billingCycle: 'monthly',
      currentPeriodStart: now,
      currentPeriodEnd: trialEnd,
      status: 'trial',
      trialStart: now,
      trialEnd: trialEnd,
      currentPrice: 0,
      autoRenew: true,
    });

    return this.repository.save(subscription);
  }

  async renewSubscription(ctx: ServiceContext): Promise<TenantSubscription | null> {
    const subscription = await this.findByTenantId(ctx.tenantId);
    if (!subscription) {
      return null;
    }

    if (!subscription.autoRenew) {
      throw new Error('Auto-renewal is disabled');
    }

    const newPeriodStart = subscription.currentPeriodEnd;
    const newPeriodEnd = this.calculatePeriodEnd(newPeriodStart, subscription.billingCycle);

    subscription.currentPeriodStart = newPeriodStart;
    subscription.currentPeriodEnd = newPeriodEnd;
    subscription.status = 'active';
    subscription.nextInvoiceDate = newPeriodEnd;

    return this.repository.save(subscription);
  }

  async recordPayment(
    ctx: ServiceContext,
    amount: number,
    paymentDate: Date = new Date()
  ): Promise<TenantSubscription | null> {
    const subscription = await this.findByTenantId(ctx.tenantId);
    if (!subscription) {
      return null;
    }

    subscription.lastPaymentAt = paymentDate;
    subscription.lastPaymentAmount = amount;

    if (subscription.status === 'past_due') {
      subscription.status = 'active';
    }

    return this.repository.save(subscription);
  }

  async getExpiringSubscriptions(daysAhead: number): Promise<TenantSubscription[]> {
    const now = new Date();
    const futureDate = new Date(now.getTime() + daysAhead * 24 * 60 * 60 * 1000);

    return this.repository.find({
      where: {
        status: 'active',
        currentPeriodEnd: Between(now, futureDate),
      },
      relations: ['plan'],
    });
  }

  async getTrialsEndingSoon(daysAhead: number): Promise<TenantSubscription[]> {
    const now = new Date();
    const futureDate = new Date(now.getTime() + daysAhead * 24 * 60 * 60 * 1000);

    return this.repository.find({
      where: {
        status: 'trial',
        trialEnd: Between(now, futureDate),
      },
      relations: ['plan'],
    });
  }

  async getSubscriptionStats(): Promise<{
    total: number;
    byStatus: Record<SubscriptionStatus, number>;
    byPlan: Record<string, number>;
    mrr: number;
  }> {
    const subscriptions = await this.repository.find({ relations: ['plan'] });

    const byStatus: Record<SubscriptionStatus, number> = {
      trial: 0,
      active: 0,
      past_due: 0,
      cancelled: 0,
      suspended: 0,
    };

    const byPlan: Record<string, number> = {};
    let mrr = 0;

    for (const sub of subscriptions) {
      byStatus[sub.status]++;

      const planName = sub.plan?.name || 'Unknown';
      byPlan[planName] = (byPlan[planName] || 0) + 1;

      if (sub.status === 'active' || sub.status === 'trial') {
        if (sub.billingCycle === 'annual') {
          mrr += Number(sub.currentPrice) / 12;
        } else {
          mrr += Number(sub.currentPrice);
        }
      }
    }

    return {
      total: subscriptions.length,
      byStatus,
      byPlan,
      mrr: Math.round(mrr * 100) / 100,
    };
  }

  private calculatePeriodEnd(startDate: Date, billingCycle: BillingCycle): Date {
    const end = new Date(startDate);
    if (billingCycle === 'annual') {
      end.setFullYear(end.getFullYear() + 1);
    } else {
      end.setMonth(end.getMonth() + 1);
    }
    return end;
  }
}
