/**
 * Subscriptions Service
 *
 * Service for managing tenant subscriptions
 */

import { Repository, DataSource } from 'typeorm';
import {
  TenantSubscription,
  SubscriptionPlan,
  BillingCycle,
  SubscriptionStatus,
} from '../entities';
import {
  CreateTenantSubscriptionDto,
  UpdateTenantSubscriptionDto,
  CancelSubscriptionDto,
  ChangePlanDto,
  SetPaymentMethodDto,
} from '../dto';

export class SubscriptionsService {
  private subscriptionRepository: Repository<TenantSubscription>;
  private planRepository: Repository<SubscriptionPlan>;

  constructor(private dataSource: DataSource) {
    this.subscriptionRepository = dataSource.getRepository(TenantSubscription);
    this.planRepository = dataSource.getRepository(SubscriptionPlan);
  }

  /**
   * Create a new subscription
   */
  async create(dto: CreateTenantSubscriptionDto): Promise<TenantSubscription> {
    // Check if tenant already has a subscription
    const existing = await this.subscriptionRepository.findOne({
      where: { tenantId: dto.tenantId },
    });

    if (existing) {
      throw new Error('Tenant already has a subscription');
    }

    // Validate plan exists
    const plan = await this.planRepository.findOne({ where: { id: dto.planId } });
    if (!plan) {
      throw new Error('Plan not found');
    }

    const now = new Date();
    const currentPeriodStart = dto.currentPeriodStart || now;
    const currentPeriodEnd =
      dto.currentPeriodEnd || this.calculatePeriodEnd(currentPeriodStart, dto.billingCycle || 'monthly');

    const subscription = this.subscriptionRepository.create({
      tenantId: dto.tenantId,
      planId: dto.planId,
      billingCycle: dto.billingCycle || 'monthly',
      currentPeriodStart,
      currentPeriodEnd,
      status: dto.startWithTrial ? 'trial' : 'active',
      billingEmail: dto.billingEmail,
      billingName: dto.billingName,
      billingAddress: dto.billingAddress || {},
      taxId: dto.taxId,
      currentPrice: dto.currentPrice,
      discountPercent: dto.discountPercent || 0,
      discountReason: dto.discountReason,
      contractedUsers: dto.contractedUsers || plan.maxUsers,
      contractedBranches: dto.contractedBranches || plan.maxBranches,
      autoRenew: dto.autoRenew !== false,
      nextInvoiceDate: currentPeriodEnd,
    });

    // Set trial dates if starting with trial
    if (dto.startWithTrial) {
      subscription.trialStart = now;
      subscription.trialEnd = new Date(now.getTime() + (dto.trialDays || 14) * 24 * 60 * 60 * 1000);
    }

    return this.subscriptionRepository.save(subscription);
  }

  /**
   * Find subscription by tenant ID
   */
  async findByTenantId(tenantId: string): Promise<TenantSubscription | null> {
    return this.subscriptionRepository.findOne({
      where: { tenantId },
      relations: ['plan'],
    });
  }

  /**
   * Find subscription by ID
   */
  async findById(id: string): Promise<TenantSubscription | null> {
    return this.subscriptionRepository.findOne({
      where: { id },
      relations: ['plan'],
    });
  }

  /**
   * Update subscription
   */
  async update(id: string, dto: UpdateTenantSubscriptionDto): Promise<TenantSubscription> {
    const subscription = await this.findById(id);
    if (!subscription) {
      throw new Error('Subscription not found');
    }

    // If changing plan, validate it exists
    if (dto.planId && dto.planId !== subscription.planId) {
      const plan = await this.planRepository.findOne({ where: { id: dto.planId } });
      if (!plan) {
        throw new Error('Plan not found');
      }
    }

    Object.assign(subscription, dto);
    return this.subscriptionRepository.save(subscription);
  }

  /**
   * Cancel subscription
   */
  async cancel(id: string, dto: CancelSubscriptionDto): Promise<TenantSubscription> {
    const subscription = await this.findById(id);
    if (!subscription) {
      throw new Error('Subscription not found');
    }

    if (subscription.status === 'cancelled') {
      throw new Error('Subscription is already cancelled');
    }

    subscription.cancellationReason = dto.reason || '';
    subscription.cancelledAt = new Date();

    if (dto.cancelImmediately) {
      subscription.status = 'cancelled';
    } else {
      subscription.cancelAtPeriodEnd = true;
      subscription.autoRenew = false;
    }

    return this.subscriptionRepository.save(subscription);
  }

  /**
   * Reactivate cancelled subscription
   */
  async reactivate(id: string): Promise<TenantSubscription> {
    const subscription = await this.findById(id);
    if (!subscription) {
      throw new Error('Subscription not found');
    }

    if (subscription.status !== 'cancelled' && !subscription.cancelAtPeriodEnd) {
      throw new Error('Subscription is not cancelled');
    }

    subscription.status = 'active';
    subscription.cancelAtPeriodEnd = false;
    subscription.cancellationReason = null as any;
    subscription.cancelledAt = null as any;
    subscription.autoRenew = true;

    return this.subscriptionRepository.save(subscription);
  }

  /**
   * Change subscription plan
   */
  async changePlan(id: string, dto: ChangePlanDto): Promise<TenantSubscription> {
    const subscription = await this.findById(id);
    if (!subscription) {
      throw new Error('Subscription not found');
    }

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

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

    // Apply existing discount if any
    const discountedPrice = newPrice * (1 - (subscription.discountPercent || 0) / 100);

    subscription.planId = dto.newPlanId;
    subscription.currentPrice = discountedPrice;
    subscription.contractedUsers = newPlan.maxUsers;
    subscription.contractedBranches = newPlan.maxBranches;

    // If effective immediately and prorate, calculate adjustment
    // This would typically create a credit/debit memo

    return this.subscriptionRepository.save(subscription);
  }

  /**
   * Set payment method
   */
  async setPaymentMethod(id: string, dto: SetPaymentMethodDto): Promise<TenantSubscription> {
    const subscription = await this.findById(id);
    if (!subscription) {
      throw new Error('Subscription not found');
    }

    subscription.paymentMethodId = dto.paymentMethodId;
    subscription.paymentProvider = dto.paymentProvider;

    return this.subscriptionRepository.save(subscription);
  }

  /**
   * Renew subscription (for periodic billing)
   */
  async renew(id: string): Promise<TenantSubscription> {
    const subscription = await this.findById(id);
    if (!subscription) {
      throw new Error('Subscription not found');
    }

    if (!subscription.autoRenew) {
      throw new Error('Subscription auto-renew is disabled');
    }

    if (subscription.cancelAtPeriodEnd) {
      subscription.status = 'cancelled';
      return this.subscriptionRepository.save(subscription);
    }

    // Calculate new period
    const newPeriodStart = subscription.currentPeriodEnd;
    const newPeriodEnd = this.calculatePeriodEnd(newPeriodStart, subscription.billingCycle);

    subscription.currentPeriodStart = newPeriodStart;
    subscription.currentPeriodEnd = newPeriodEnd;
    subscription.nextInvoiceDate = newPeriodEnd;

    // Reset trial status if was in trial
    if (subscription.status === 'trial') {
      subscription.status = 'active';
    }

    return this.subscriptionRepository.save(subscription);
  }

  /**
   * Mark subscription as past due
   */
  async markPastDue(id: string): Promise<TenantSubscription> {
    return this.updateStatus(id, 'past_due');
  }

  /**
   * Suspend subscription
   */
  async suspend(id: string): Promise<TenantSubscription> {
    return this.updateStatus(id, 'suspended');
  }

  /**
   * Activate subscription (from suspended or past_due)
   */
  async activate(id: string): Promise<TenantSubscription> {
    return this.updateStatus(id, 'active');
  }

  /**
   * Update subscription status
   */
  private async updateStatus(id: string, status: SubscriptionStatus): Promise<TenantSubscription> {
    const subscription = await this.findById(id);
    if (!subscription) {
      throw new Error('Subscription not found');
    }

    subscription.status = status;
    return this.subscriptionRepository.save(subscription);
  }

  /**
   * Find subscriptions expiring soon
   */
  async findExpiringSoon(days: number = 7): Promise<TenantSubscription[]> {
    const futureDate = new Date();
    futureDate.setDate(futureDate.getDate() + days);

    return this.subscriptionRepository
      .createQueryBuilder('sub')
      .leftJoinAndSelect('sub.plan', 'plan')
      .where('sub.currentPeriodEnd <= :futureDate', { futureDate })
      .andWhere("sub.status IN ('active', 'trial')")
      .andWhere('sub.cancelAtPeriodEnd = false')
      .orderBy('sub.currentPeriodEnd', 'ASC')
      .getMany();
  }

  /**
   * Find subscriptions with trials ending soon
   */
  async findTrialsEndingSoon(days: number = 3): Promise<TenantSubscription[]> {
    const futureDate = new Date();
    futureDate.setDate(futureDate.getDate() + days);

    return this.subscriptionRepository
      .createQueryBuilder('sub')
      .leftJoinAndSelect('sub.plan', 'plan')
      .where("sub.status = 'trial'")
      .andWhere('sub.trialEnd <= :futureDate', { futureDate })
      .orderBy('sub.trialEnd', 'ASC')
      .getMany();
  }

  /**
   * Calculate period end date based on billing cycle
   */
  private calculatePeriodEnd(start: Date, cycle: BillingCycle): Date {
    const end = new Date(start);
    if (cycle === 'annual') {
      end.setFullYear(end.getFullYear() + 1);
    } else {
      end.setMonth(end.getMonth() + 1);
    }
    return end;
  }

  /**
   * Get subscription statistics
   */
  async getStats(): Promise<{
    total: number;
    byStatus: Record<SubscriptionStatus, number>;
    byPlan: Record<string, number>;
    totalMRR: number;
    totalARR: number;
  }> {
    const subscriptions = await this.subscriptionRepository.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 totalMRR = 0;

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

      const planCode = sub.plan?.code || 'unknown';
      byPlan[planCode] = (byPlan[planCode] || 0) + 1;

      if (sub.status === 'active' || sub.status === 'trial') {
        const monthlyPrice =
          sub.billingCycle === 'annual'
            ? Number(sub.currentPrice) / 12
            : Number(sub.currentPrice);
        totalMRR += monthlyPrice;
      }
    }

    return {
      total: subscriptions.length,
      byStatus,
      byPlan,
      totalMRR,
      totalARR: totalMRR * 12,
    };
  }
}
