/**
 * Usage Tracking Service
 *
 * Service for tracking and reporting usage metrics
 */

import { Repository, DataSource, Between, MoreThanOrEqual, LessThanOrEqual } from 'typeorm';
import { UsageTracking, TenantSubscription, SubscriptionPlan } from '../entities';
import { RecordUsageDto, UpdateUsageDto, UsageMetrics, UsageSummaryDto } from '../dto';

export class UsageTrackingService {
  private usageRepository: Repository<UsageTracking>;
  private subscriptionRepository: Repository<TenantSubscription>;
  private planRepository: Repository<SubscriptionPlan>;

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

  /**
   * Record usage for a period
   */
  async recordUsage(dto: RecordUsageDto): Promise<UsageTracking> {
    // Check if record exists for this tenant/period
    const existing = await this.usageRepository.findOne({
      where: {
        tenantId: dto.tenantId,
        periodStart: dto.periodStart,
      },
    });

    if (existing) {
      // Update existing record
      return this.update(existing.id, dto);
    }

    const usage = this.usageRepository.create({
      tenantId: dto.tenantId,
      periodStart: dto.periodStart,
      periodEnd: dto.periodEnd,
      activeUsers: dto.activeUsers || 0,
      peakConcurrentUsers: dto.peakConcurrentUsers || 0,
      usersByProfile: dto.usersByProfile || {},
      usersByPlatform: dto.usersByPlatform || {},
      activeBranches: dto.activeBranches || 0,
      storageUsedGb: dto.storageUsedGb || 0,
      documentsCount: dto.documentsCount || 0,
      apiCalls: dto.apiCalls || 0,
      apiErrors: dto.apiErrors || 0,
      salesCount: dto.salesCount || 0,
      salesAmount: dto.salesAmount || 0,
      invoicesGenerated: dto.invoicesGenerated || 0,
      mobileSessions: dto.mobileSessions || 0,
      offlineSyncs: dto.offlineSyncs || 0,
      paymentTransactions: dto.paymentTransactions || 0,
    });

    // Calculate billable amount
    usage.totalBillableAmount = await this.calculateBillableAmount(dto.tenantId, usage);

    return this.usageRepository.save(usage);
  }

  /**
   * Update usage record
   */
  async update(id: string, dto: UpdateUsageDto): Promise<UsageTracking> {
    const usage = await this.usageRepository.findOne({ where: { id } });
    if (!usage) {
      throw new Error('Usage record not found');
    }

    Object.assign(usage, dto);
    usage.totalBillableAmount = await this.calculateBillableAmount(usage.tenantId, usage);

    return this.usageRepository.save(usage);
  }

  /**
   * Increment a specific metric
   */
  async incrementMetric(
    tenantId: string,
    metric: keyof UsageMetrics,
    amount: number = 1
  ): Promise<void> {
    const currentPeriod = this.getCurrentPeriodDates();

    let usage = await this.usageRepository.findOne({
      where: {
        tenantId,
        periodStart: currentPeriod.start,
      },
    });

    if (!usage) {
      usage = await this.recordUsage({
        tenantId,
        periodStart: currentPeriod.start,
        periodEnd: currentPeriod.end,
      });
    }

    // Increment the specific metric
    (usage as any)[metric] = ((usage as any)[metric] || 0) + amount;

    await this.usageRepository.save(usage);
  }

  /**
   * Get current usage for tenant
   */
  async getCurrentUsage(tenantId: string): Promise<UsageTracking | null> {
    const currentPeriod = this.getCurrentPeriodDates();

    return this.usageRepository.findOne({
      where: {
        tenantId,
        periodStart: currentPeriod.start,
      },
    });
  }

  /**
   * Get usage history for tenant
   */
  async getUsageHistory(
    tenantId: string,
    startDate: Date,
    endDate: Date
  ): Promise<UsageTracking[]> {
    return this.usageRepository.find({
      where: {
        tenantId,
        periodStart: MoreThanOrEqual(startDate),
        periodEnd: LessThanOrEqual(endDate),
      },
      order: { periodStart: 'DESC' },
    });
  }

  /**
   * Get usage summary with limits comparison
   */
  async getUsageSummary(tenantId: string): Promise<UsageSummaryDto> {
    const subscription = await this.subscriptionRepository.findOne({
      where: { tenantId },
      relations: ['plan'],
    });

    if (!subscription) {
      throw new Error('Subscription not found');
    }

    const currentUsage = await this.getCurrentUsage(tenantId);
    const plan = subscription.plan;

    const summary: UsageSummaryDto = {
      tenantId,
      currentUsers: currentUsage?.activeUsers || 0,
      currentBranches: currentUsage?.activeBranches || 0,
      currentStorageGb: Number(currentUsage?.storageUsedGb || 0),
      apiCallsThisMonth: currentUsage?.apiCalls || 0,
      salesThisMonth: currentUsage?.salesCount || 0,
      salesAmountThisMonth: Number(currentUsage?.salesAmount || 0),
      limits: {
        maxUsers: subscription.contractedUsers || plan.maxUsers,
        maxBranches: subscription.contractedBranches || plan.maxBranches,
        maxStorageGb: plan.storageGb,
        maxApiCalls: plan.apiCallsMonthly,
      },
      percentages: {
        usersUsed: 0,
        branchesUsed: 0,
        storageUsed: 0,
        apiCallsUsed: 0,
      },
    };

    // Calculate percentages
    summary.percentages.usersUsed =
      Math.round((summary.currentUsers / summary.limits.maxUsers) * 100);
    summary.percentages.branchesUsed =
      Math.round((summary.currentBranches / summary.limits.maxBranches) * 100);
    summary.percentages.storageUsed =
      Math.round((summary.currentStorageGb / summary.limits.maxStorageGb) * 100);
    summary.percentages.apiCallsUsed =
      Math.round((summary.apiCallsThisMonth / summary.limits.maxApiCalls) * 100);

    return summary;
  }

  /**
   * Check if tenant exceeds limits
   */
  async checkLimits(tenantId: string): Promise<{
    exceeds: boolean;
    violations: string[];
    warnings: string[];
  }> {
    const summary = await this.getUsageSummary(tenantId);
    const violations: string[] = [];
    const warnings: string[] = [];

    // Check hard limits
    if (summary.currentUsers > summary.limits.maxUsers) {
      violations.push(`Users: ${summary.currentUsers}/${summary.limits.maxUsers}`);
    }

    if (summary.currentBranches > summary.limits.maxBranches) {
      violations.push(`Branches: ${summary.currentBranches}/${summary.limits.maxBranches}`);
    }

    if (summary.currentStorageGb > summary.limits.maxStorageGb) {
      violations.push(
        `Storage: ${summary.currentStorageGb}GB/${summary.limits.maxStorageGb}GB`
      );
    }

    // Check warnings (80% threshold)
    if (summary.percentages.usersUsed >= 80 && summary.percentages.usersUsed < 100) {
      warnings.push(`Users at ${summary.percentages.usersUsed}% capacity`);
    }

    if (summary.percentages.branchesUsed >= 80 && summary.percentages.branchesUsed < 100) {
      warnings.push(`Branches at ${summary.percentages.branchesUsed}% capacity`);
    }

    if (summary.percentages.storageUsed >= 80 && summary.percentages.storageUsed < 100) {
      warnings.push(`Storage at ${summary.percentages.storageUsed}% capacity`);
    }

    if (summary.percentages.apiCallsUsed >= 80 && summary.percentages.apiCallsUsed < 100) {
      warnings.push(`API calls at ${summary.percentages.apiCallsUsed}% capacity`);
    }

    return {
      exceeds: violations.length > 0,
      violations,
      warnings,
    };
  }

  /**
   * Get usage report
   */
  async getUsageReport(
    tenantId: string,
    startDate: Date,
    endDate: Date,
    granularity: 'daily' | 'weekly' | 'monthly' = 'monthly'
  ): Promise<{
    tenantId: string;
    startDate: Date;
    endDate: Date;
    granularity: string;
    data: UsageTracking[];
    totals: {
      apiCalls: number;
      salesCount: number;
      salesAmount: number;
      mobileSessions: number;
      paymentTransactions: number;
    };
    averages: {
      activeUsers: number;
      activeBranches: number;
      storageUsedGb: number;
    };
  }> {
    const data = await this.getUsageHistory(tenantId, startDate, endDate);

    // Calculate totals
    const totals = {
      apiCalls: 0,
      salesCount: 0,
      salesAmount: 0,
      mobileSessions: 0,
      paymentTransactions: 0,
    };

    let totalUsers = 0;
    let totalBranches = 0;
    let totalStorage = 0;

    for (const record of data) {
      totals.apiCalls += record.apiCalls;
      totals.salesCount += record.salesCount;
      totals.salesAmount += Number(record.salesAmount);
      totals.mobileSessions += record.mobileSessions;
      totals.paymentTransactions += record.paymentTransactions;

      totalUsers += record.activeUsers;
      totalBranches += record.activeBranches;
      totalStorage += Number(record.storageUsedGb);
    }

    const count = data.length || 1;

    return {
      tenantId,
      startDate,
      endDate,
      granularity,
      data,
      totals,
      averages: {
        activeUsers: Math.round(totalUsers / count),
        activeBranches: Math.round(totalBranches / count),
        storageUsedGb: Math.round((totalStorage / count) * 100) / 100,
      },
    };
  }

  /**
   * Calculate billable amount based on usage
   */
  private async calculateBillableAmount(
    tenantId: string,
    usage: UsageTracking
  ): Promise<number> {
    const subscription = await this.subscriptionRepository.findOne({
      where: { tenantId },
      relations: ['plan'],
    });

    if (!subscription) {
      return 0;
    }

    let billableAmount = Number(subscription.currentPrice);

    // Add overage charges if applicable
    const plan = subscription.plan;

    // Extra users
    const extraUsers = Math.max(0, usage.activeUsers - (subscription.contractedUsers || plan.maxUsers));
    if (extraUsers > 0) {
      // Assume $10 per extra user per month
      billableAmount += extraUsers * 10;
    }

    // Extra branches
    const extraBranches = Math.max(
      0,
      usage.activeBranches - (subscription.contractedBranches || plan.maxBranches)
    );
    if (extraBranches > 0) {
      // Assume $20 per extra branch per month
      billableAmount += extraBranches * 20;
    }

    // Extra storage
    const extraStorageGb = Math.max(0, Number(usage.storageUsedGb) - plan.storageGb);
    if (extraStorageGb > 0) {
      // Assume $0.50 per extra GB
      billableAmount += extraStorageGb * 0.5;
    }

    // Extra API calls
    const extraApiCalls = Math.max(0, usage.apiCalls - plan.apiCallsMonthly);
    if (extraApiCalls > 0) {
      // Assume $0.001 per extra API call
      billableAmount += extraApiCalls * 0.001;
    }

    return billableAmount;
  }

  /**
   * Get current period dates (first and last day of current month)
   */
  private getCurrentPeriodDates(): { start: Date; end: Date } {
    const now = new Date();
    const start = new Date(now.getFullYear(), now.getMonth(), 1);
    const end = new Date(now.getFullYear(), now.getMonth() + 1, 0);
    return { start, end };
  }
}
