/**
 * Usage Event Service
 * Records granular usage events for billing calculations
 *
 * @module BillingUsage
 */

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

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

export interface CreateUsageEventDto {
  userId?: string;
  deviceId?: string;
  branchId?: string;
  eventType: string;
  eventCategory: EventCategory;
  profileCode?: string;
  platform?: string;
  resourceId?: string;
  resourceType?: string;
  quantity?: number;
  bytesUsed?: number;
  durationMs?: number;
  metadata?: Record<string, any>;
}

export interface UsageEventFilters {
  eventType?: string;
  eventCategory?: EventCategory;
  userId?: string;
  branchId?: string;
  platform?: string;
  startDate?: Date;
  endDate?: Date;
}

export interface EventAggregation {
  eventType: string;
  count: number;
  totalQuantity: number;
  totalBytes: number;
}

export class UsageEventService {
  private repository: Repository<UsageEvent>;

  constructor() {
    this.repository = AppDataSource.getRepository(UsageEvent);
  }

  async record(ctx: ServiceContext, data: CreateUsageEventDto): Promise<UsageEvent> {
    const event = this.repository.create({
      ...data,
      tenantId: ctx.tenantId,
    });
    return this.repository.save(event);
  }

  async recordBatch(
    ctx: ServiceContext,
    events: CreateUsageEventDto[]
  ): Promise<UsageEvent[]> {
    const entities = events.map((data) =>
      this.repository.create({
        ...data,
        tenantId: ctx.tenantId,
      })
    );
    return this.repository.save(entities);
  }

  async findByFilters(
    ctx: ServiceContext,
    filters: UsageEventFilters,
    limit: number = 100,
    offset: number = 0
  ): Promise<{ events: UsageEvent[]; total: number }> {
    const where: FindOptionsWhere<UsageEvent> = {
      tenantId: ctx.tenantId,
    };

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

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

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

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

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

    if (filters.startDate && filters.endDate) {
      where.createdAt = Between(filters.startDate, filters.endDate);
    } else if (filters.startDate) {
      where.createdAt = MoreThanOrEqual(filters.startDate);
    }

    const [events, total] = await this.repository.findAndCount({
      where,
      order: { createdAt: 'DESC' },
      take: limit,
      skip: offset,
    });

    return { events, total };
  }

  async getRecentEvents(
    ctx: ServiceContext,
    limit: number = 50
  ): Promise<UsageEvent[]> {
    return this.repository.find({
      where: { tenantId: ctx.tenantId },
      order: { createdAt: 'DESC' },
      take: limit,
    });
  }

  async countByCategory(
    ctx: ServiceContext,
    startDate: Date,
    endDate: Date
  ): Promise<Record<EventCategory, number>> {
    const categories: EventCategory[] = ['user', 'api', 'storage', 'transaction', 'mobile'];
    const result: Record<EventCategory, number> = {
      user: 0,
      api: 0,
      storage: 0,
      transaction: 0,
      mobile: 0,
    };

    for (const category of categories) {
      result[category] = await this.repository.count({
        where: {
          tenantId: ctx.tenantId,
          eventCategory: category,
          createdAt: Between(startDate, endDate),
        },
      });
    }

    return result;
  }

  async countByEventType(
    ctx: ServiceContext,
    startDate: Date,
    endDate: Date
  ): Promise<{ eventType: string; count: number }[]> {
    const events = await this.repository.find({
      where: {
        tenantId: ctx.tenantId,
        createdAt: Between(startDate, endDate),
      },
      select: ['eventType'],
    });

    const countMap: Record<string, number> = {};
    for (const event of events) {
      countMap[event.eventType] = (countMap[event.eventType] || 0) + 1;
    }

    return Object.entries(countMap)
      .map(([eventType, count]) => ({ eventType, count }))
      .sort((a, b) => b.count - a.count);
  }

  async getApiUsage(
    ctx: ServiceContext,
    startDate: Date,
    endDate: Date
  ): Promise<{
    totalCalls: number;
    totalErrors: number;
    avgDurationMs: number;
    byEndpoint: Record<string, number>;
  }> {
    const events = await this.repository.find({
      where: {
        tenantId: ctx.tenantId,
        eventCategory: 'api',
        createdAt: Between(startDate, endDate),
      },
    });

    let totalCalls = events.length;
    let totalErrors = 0;
    let totalDuration = 0;
    const byEndpoint: Record<string, number> = {};

    for (const event of events) {
      if (event.metadata?.error) {
        totalErrors++;
      }
      if (event.durationMs) {
        totalDuration += event.durationMs;
      }
      const endpoint = event.resourceType || 'unknown';
      byEndpoint[endpoint] = (byEndpoint[endpoint] || 0) + 1;
    }

    return {
      totalCalls,
      totalErrors,
      avgDurationMs: totalCalls > 0 ? Math.round(totalDuration / totalCalls) : 0,
      byEndpoint,
    };
  }

  async getStorageUsage(
    ctx: ServiceContext,
    startDate: Date,
    endDate: Date
  ): Promise<{
    totalBytes: number;
    totalDocuments: number;
    byType: Record<string, number>;
  }> {
    const events = await this.repository.find({
      where: {
        tenantId: ctx.tenantId,
        eventCategory: 'storage',
        createdAt: Between(startDate, endDate),
      },
    });

    let totalBytes = 0;
    let totalDocuments = 0;
    const byType: Record<string, number> = {};

    for (const event of events) {
      totalBytes += Number(event.bytesUsed) || 0;
      totalDocuments += event.quantity || 1;
      const docType = event.resourceType || 'unknown';
      byType[docType] = (byType[docType] || 0) + (Number(event.bytesUsed) || 0);
    }

    return {
      totalBytes,
      totalDocuments,
      byType,
    };
  }

  async getUserActivity(
    ctx: ServiceContext,
    startDate: Date,
    endDate: Date
  ): Promise<{
    uniqueUsers: number;
    totalSessions: number;
    byProfile: Record<string, number>;
    byPlatform: Record<string, number>;
  }> {
    const events = await this.repository.find({
      where: {
        tenantId: ctx.tenantId,
        eventCategory: 'user',
        createdAt: Between(startDate, endDate),
      },
    });

    const uniqueUserIds = new Set<string>();
    let totalSessions = 0;
    const byProfile: Record<string, number> = {};
    const byPlatform: Record<string, number> = {};

    for (const event of events) {
      if (event.userId) {
        uniqueUserIds.add(event.userId);
      }
      if (event.eventType === 'login' || event.eventType === 'session_start') {
        totalSessions++;
      }
      if (event.profileCode) {
        byProfile[event.profileCode] = (byProfile[event.profileCode] || 0) + 1;
      }
      if (event.platform) {
        byPlatform[event.platform] = (byPlatform[event.platform] || 0) + 1;
      }
    }

    return {
      uniqueUsers: uniqueUserIds.size,
      totalSessions,
      byProfile,
      byPlatform,
    };
  }

  async recordLogin(
    ctx: ServiceContext,
    userId: string,
    profileCode: string,
    platform: string,
    metadata?: Record<string, any>
  ): Promise<UsageEvent> {
    return this.record(ctx, {
      userId,
      eventType: 'login',
      eventCategory: 'user',
      profileCode,
      platform,
      metadata,
    });
  }

  async recordApiCall(
    ctx: ServiceContext,
    endpoint: string,
    durationMs: number,
    error?: string
  ): Promise<UsageEvent> {
    return this.record(ctx, {
      eventType: 'api_call',
      eventCategory: 'api',
      resourceType: endpoint,
      durationMs,
      metadata: error ? { error } : undefined,
    });
  }

  async recordDocumentUpload(
    ctx: ServiceContext,
    userId: string,
    documentId: string,
    documentType: string,
    bytesUsed: number
  ): Promise<UsageEvent> {
    return this.record(ctx, {
      userId,
      eventType: 'document_upload',
      eventCategory: 'storage',
      resourceId: documentId,
      resourceType: documentType,
      bytesUsed,
      quantity: 1,
    });
  }

  async recordSale(
    ctx: ServiceContext,
    userId: string,
    saleId: string,
    branchId: string,
    amount: number
  ): Promise<UsageEvent> {
    return this.record(ctx, {
      userId,
      branchId,
      eventType: 'sale',
      eventCategory: 'transaction',
      resourceId: saleId,
      resourceType: 'sale',
      metadata: { amount },
    });
  }

  async recordMobileSync(
    ctx: ServiceContext,
    userId: string,
    deviceId: string,
    recordsSynced: number
  ): Promise<UsageEvent> {
    return this.record(ctx, {
      userId,
      deviceId,
      eventType: 'sync',
      eventCategory: 'mobile',
      quantity: recordsSynced,
    });
  }

  async cleanupOldEvents(retentionDays: number): Promise<number> {
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - retentionDays);

    const result = await this.repository
      .createQueryBuilder()
      .delete()
      .where('created_at < :cutoffDate', { cutoffDate })
      .execute();

    return result.affected || 0;
  }
}
