/**
 * Usage Tracking Service
 * Tracks and aggregates usage metrics for billing
 *
 * @module BillingUsage
 */

import { Repository, Between, MoreThanOrEqual, LessThanOrEqual } from 'typeorm';
import { AppDataSource } from '../../../shared/database/typeorm.config';
import { UsageTracking } from '../entities/usage-tracking.entity';
import { UsageEvent, EventCategory } from '../entities/usage-event.entity';

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

export interface CreateUsageTrackingDto {
  periodStart: Date;
  periodEnd: Date;
  activeUsers?: number;
  peakConcurrentUsers?: number;
  usersByProfile?: Record<string, number>;
  usersByPlatform?: Record<string, number>;
  activeBranches?: number;
  storageUsedGb?: number;
  documentsCount?: number;
  apiCalls?: number;
  apiErrors?: number;
  salesCount?: number;
  salesAmount?: number;
  invoicesGenerated?: number;
  mobileSessions?: number;
  offlineSyncs?: number;
  paymentTransactions?: number;
  totalBillableAmount?: number;
}

export interface UpdateUsageTrackingDto extends Partial<CreateUsageTrackingDto> {}

export interface UsageTrackingFilters {
  periodStart?: Date;
  periodEnd?: Date;
}

export interface UsageSummary {
  totalUsers: number;
  totalBranches: number;
  totalStorageGb: number;
  totalApiCalls: number;
  totalSalesAmount: number;
  avgDailyUsers: number;
  peakUsers: number;
}

export class UsageTrackingService {
  private trackingRepository: Repository<UsageTracking>;
  private eventRepository: Repository<UsageEvent>;

  constructor() {
    this.trackingRepository = AppDataSource.getRepository(UsageTracking);
    this.eventRepository = AppDataSource.getRepository(UsageEvent);
  }

  async getCurrentPeriod(ctx: ServiceContext): Promise<UsageTracking | null> {
    const now = new Date();
    return this.trackingRepository.findOne({
      where: {
        tenantId: ctx.tenantId,
        periodStart: LessThanOrEqual(now),
        periodEnd: MoreThanOrEqual(now),
      },
    });
  }

  async findByPeriod(
    ctx: ServiceContext,
    periodStart: Date,
    periodEnd: Date
  ): Promise<UsageTracking[]> {
    return this.trackingRepository.find({
      where: {
        tenantId: ctx.tenantId,
        periodStart: MoreThanOrEqual(periodStart),
        periodEnd: LessThanOrEqual(periodEnd),
      },
      order: { periodStart: 'DESC' },
    });
  }

  async getUsageHistory(
    ctx: ServiceContext,
    months: number = 12
  ): Promise<UsageTracking[]> {
    const startDate = new Date();
    startDate.setMonth(startDate.getMonth() - months);

    return this.trackingRepository.find({
      where: {
        tenantId: ctx.tenantId,
        periodStart: MoreThanOrEqual(startDate),
      },
      order: { periodStart: 'DESC' },
    });
  }

  async create(ctx: ServiceContext, data: CreateUsageTrackingDto): Promise<UsageTracking> {
    const tracking = this.trackingRepository.create({
      ...data,
      tenantId: ctx.tenantId,
    });
    return this.trackingRepository.save(tracking);
  }

  async update(
    ctx: ServiceContext,
    id: string,
    data: UpdateUsageTrackingDto
  ): Promise<UsageTracking | null> {
    const tracking = await this.trackingRepository.findOne({
      where: { id, tenantId: ctx.tenantId },
    });
    if (!tracking) {
      return null;
    }

    Object.assign(tracking, data);
    return this.trackingRepository.save(tracking);
  }

  async updateCurrentPeriod(
    ctx: ServiceContext,
    data: UpdateUsageTrackingDto
  ): Promise<UsageTracking | null> {
    const current = await this.getCurrentPeriod(ctx);
    if (!current) {
      return null;
    }

    Object.assign(current, data);
    return this.trackingRepository.save(current);
  }

  async incrementMetric(
    ctx: ServiceContext,
    metric: keyof UsageTracking,
    value: number = 1
  ): Promise<UsageTracking | null> {
    const current = await this.getCurrentPeriod(ctx);
    if (!current) {
      return null;
    }

    const currentValue = (current[metric] as number) || 0;
    (current as any)[metric] = currentValue + value;

    return this.trackingRepository.save(current);
  }

  async recordUserActivity(
    ctx: ServiceContext,
    profileCode: string,
    platform: string
  ): Promise<void> {
    const current = await this.getCurrentPeriod(ctx);
    if (!current) {
      return;
    }

    // Update users by profile
    const usersByProfile = current.usersByProfile || {};
    usersByProfile[profileCode] = (usersByProfile[profileCode] || 0) + 1;
    current.usersByProfile = usersByProfile;

    // Update users by platform
    const usersByPlatform = current.usersByPlatform || {};
    usersByPlatform[platform] = (usersByPlatform[platform] || 0) + 1;
    current.usersByPlatform = usersByPlatform;

    await this.trackingRepository.save(current);
  }

  async getUsageSummary(
    ctx: ServiceContext,
    startDate: Date,
    endDate: Date
  ): Promise<UsageSummary> {
    const trackings = await this.trackingRepository.find({
      where: {
        tenantId: ctx.tenantId,
        periodStart: Between(startDate, endDate),
      },
    });

    if (trackings.length === 0) {
      return {
        totalUsers: 0,
        totalBranches: 0,
        totalStorageGb: 0,
        totalApiCalls: 0,
        totalSalesAmount: 0,
        avgDailyUsers: 0,
        peakUsers: 0,
      };
    }

    let totalApiCalls = 0;
    let totalSalesAmount = 0;
    let peakUsers = 0;
    let totalDailyUsers = 0;

    for (const t of trackings) {
      totalApiCalls += t.apiCalls || 0;
      totalSalesAmount += Number(t.salesAmount) || 0;
      peakUsers = Math.max(peakUsers, t.peakConcurrentUsers || 0);
      totalDailyUsers += t.activeUsers || 0;
    }

    const latest = trackings[0];

    return {
      totalUsers: latest.activeUsers || 0,
      totalBranches: latest.activeBranches || 0,
      totalStorageGb: Number(latest.storageUsedGb) || 0,
      totalApiCalls,
      totalSalesAmount,
      avgDailyUsers: Math.round(totalDailyUsers / trackings.length),
      peakUsers,
    };
  }

  async getUsageTrend(
    ctx: ServiceContext,
    metric: keyof UsageTracking,
    periods: number = 6
  ): Promise<{ period: string; value: number }[]> {
    const trackings = await this.getUsageHistory(ctx, periods);

    return trackings.map((t) => ({
      period: t.periodStart.toISOString().slice(0, 7), // YYYY-MM
      value: Number(t[metric]) || 0,
    })).reverse();
  }

  async initializePeriod(
    ctx: ServiceContext,
    periodStart: Date,
    periodEnd: Date
  ): Promise<UsageTracking> {
    // Check if period already exists
    const existing = await this.trackingRepository.findOne({
      where: {
        tenantId: ctx.tenantId,
        periodStart,
      },
    });

    if (existing) {
      return existing;
    }

    // Create new period with zero values
    return this.create(ctx, {
      periodStart,
      periodEnd,
      activeUsers: 0,
      peakConcurrentUsers: 0,
      usersByProfile: {},
      usersByPlatform: {},
      activeBranches: 0,
      storageUsedGb: 0,
      documentsCount: 0,
      apiCalls: 0,
      apiErrors: 0,
      salesCount: 0,
      salesAmount: 0,
      invoicesGenerated: 0,
      mobileSessions: 0,
      offlineSyncs: 0,
      paymentTransactions: 0,
      totalBillableAmount: 0,
    });
  }

  async aggregateFromEvents(
    ctx: ServiceContext,
    periodStart: Date,
    periodEnd: Date
  ): Promise<UsageTracking | null> {
    const tracking = await this.trackingRepository.findOne({
      where: {
        tenantId: ctx.tenantId,
        periodStart,
      },
    });

    if (!tracking) {
      return null;
    }

    // Aggregate API calls
    const apiEvents = await this.eventRepository.count({
      where: {
        tenantId: ctx.tenantId,
        eventCategory: 'api' as EventCategory,
        createdAt: Between(periodStart, periodEnd),
      },
    });

    // Aggregate storage events
    const storageEvents = await this.eventRepository.find({
      where: {
        tenantId: ctx.tenantId,
        eventCategory: 'storage' as EventCategory,
        createdAt: Between(periodStart, periodEnd),
      },
    });

    let totalBytes = 0;
    for (const event of storageEvents) {
      totalBytes += Number(event.bytesUsed) || 0;
    }

    // Aggregate transaction events
    const transactionEvents = await this.eventRepository.count({
      where: {
        tenantId: ctx.tenantId,
        eventCategory: 'transaction' as EventCategory,
        createdAt: Between(periodStart, periodEnd),
      },
    });

    // Aggregate mobile events
    const mobileEvents = await this.eventRepository.count({
      where: {
        tenantId: ctx.tenantId,
        eventCategory: 'mobile' as EventCategory,
        createdAt: Between(periodStart, periodEnd),
      },
    });

    tracking.apiCalls = apiEvents;
    tracking.storageUsedGb = totalBytes / (1024 * 1024 * 1024);
    tracking.salesCount = transactionEvents;
    tracking.mobileSessions = mobileEvents;

    return this.trackingRepository.save(tracking);
  }
}
