/**
 * DataExport Service
 * GDPR/reporting data export request management.
 *
 * @module Audit
 */

import { Repository } from 'typeorm';
import {
  DataExport,
  ExportType,
  ExportFormat,
  ExportStatus,
} from '../entities/data-export.entity';
import { ServiceContext, PaginatedResult } from './audit-log.service';

export interface CreateDataExportDto {
  userId: string;
  exportType: ExportType;
  exportFormat?: ExportFormat;
  entityTypes: string[];
  filters?: Record<string, any>;
  dateRangeStart?: Date;
  dateRangeEnd?: Date;
  ipAddress?: string;
  userAgent?: string;
}

export interface UpdateDataExportDto {
  status?: ExportStatus;
  recordCount?: number;
  fileSizeBytes?: number;
  fileHash?: string;
  downloadUrl?: string;
  downloadExpiresAt?: Date;
  completedAt?: Date;
  expiresAt?: Date;
}

export interface DataExportFilters {
  userId?: string;
  exportType?: ExportType;
  exportFormat?: ExportFormat;
  status?: ExportStatus;
  dateFrom?: Date;
  dateTo?: Date;
}

export interface DataExportStats {
  period: { days: number; from: Date; to: Date };
  totalExports: number;
  byStatus: { status: string; count: number }[];
  byType: { type: string; count: number }[];
  byFormat: { format: string; count: number }[];
  totalRecordsExported: number;
  totalBytesExported: number;
}

export class DataExportService {
  constructor(private readonly repository: Repository<DataExport>) {}

  /**
   * Create a new export request
   */
  async create(
    ctx: ServiceContext,
    dto: CreateDataExportDto,
  ): Promise<DataExport> {
    const dataExport = this.repository.create({
      tenantId: ctx.tenantId,
      userId: dto.userId,
      exportType: dto.exportType,
      exportFormat: dto.exportFormat,
      entityTypes: dto.entityTypes,
      filters: dto.filters || {},
      dateRangeStart: dto.dateRangeStart,
      dateRangeEnd: dto.dateRangeEnd,
      ipAddress: dto.ipAddress,
      userAgent: dto.userAgent,
      status: 'pending',
    });

    return this.repository.save(dataExport);
  }

  /**
   * Find export by ID
   */
  async findById(ctx: ServiceContext, id: string): Promise<DataExport | null> {
    return this.repository.findOne({
      where: { id, tenantId: ctx.tenantId },
    });
  }

  /**
   * Update export status and metadata
   */
  async update(
    ctx: ServiceContext,
    id: string,
    dto: UpdateDataExportDto,
  ): Promise<DataExport | null> {
    const dataExport = await this.findById(ctx, id);
    if (!dataExport) {
      return null;
    }

    Object.assign(dataExport, dto);
    return this.repository.save(dataExport);
  }

  /**
   * Mark export as processing
   */
  async markProcessing(ctx: ServiceContext, id: string): Promise<DataExport | null> {
    return this.update(ctx, id, { status: 'processing' });
  }

  /**
   * Mark export as completed
   */
  async markCompleted(
    ctx: ServiceContext,
    id: string,
    recordCount: number,
    fileSizeBytes: number,
    fileHash: string,
    downloadUrl: string,
    expiresInHours = 24,
  ): Promise<DataExport | null> {
    const downloadExpiresAt = new Date();
    downloadExpiresAt.setHours(downloadExpiresAt.getHours() + expiresInHours);

    return this.update(ctx, id, {
      status: 'completed',
      recordCount,
      fileSizeBytes,
      fileHash,
      downloadUrl,
      downloadExpiresAt,
      completedAt: new Date(),
    });
  }

  /**
   * Mark export as failed
   */
  async markFailed(ctx: ServiceContext, id: string): Promise<DataExport | null> {
    return this.update(ctx, id, { status: 'failed' });
  }

  /**
   * Increment download count
   */
  async incrementDownloadCount(ctx: ServiceContext, id: string): Promise<void> {
    await this.repository
      .createQueryBuilder()
      .update(DataExport)
      .set({ downloadCount: () => 'download_count + 1' })
      .where('id = :id', { id })
      .andWhere('tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .execute();
  }

  /**
   * Find exports with filters
   */
  async findWithFilters(
    ctx: ServiceContext,
    filters: DataExportFilters,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<DataExport>> {
    const qb = this.repository
      .createQueryBuilder('de')
      .where('de.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (filters.userId) {
      qb.andWhere('de.user_id = :userId', { userId: filters.userId });
    }
    if (filters.exportType) {
      qb.andWhere('de.export_type = :exportType', { exportType: filters.exportType });
    }
    if (filters.exportFormat) {
      qb.andWhere('de.export_format = :exportFormat', { exportFormat: filters.exportFormat });
    }
    if (filters.status) {
      qb.andWhere('de.status = :status', { status: filters.status });
    }
    if (filters.dateFrom) {
      qb.andWhere('de.requested_at >= :dateFrom', { dateFrom: filters.dateFrom });
    }
    if (filters.dateTo) {
      qb.andWhere('de.requested_at <= :dateTo', { dateTo: filters.dateTo });
    }

    const skip = (page - 1) * limit;
    qb.orderBy('de.requested_at', 'DESC').skip(skip).take(limit);

    const [data, total] = await qb.getManyAndCount();

    return {
      data,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  }

  /**
   * Get exports for a user
   */
  async findByUser(
    ctx: ServiceContext,
    userId: string,
    page = 1,
    limit = 20,
  ): Promise<PaginatedResult<DataExport>> {
    return this.findWithFilters(ctx, { userId }, page, limit);
  }

  /**
   * Get pending exports
   */
  async getPendingExports(ctx: ServiceContext): Promise<DataExport[]> {
    return this.repository.find({
      where: { tenantId: ctx.tenantId, status: 'pending' },
      order: { requestedAt: 'ASC' },
    });
  }

  /**
   * Get processing exports
   */
  async getProcessingExports(ctx: ServiceContext): Promise<DataExport[]> {
    return this.repository.find({
      where: { tenantId: ctx.tenantId, status: 'processing' },
      order: { requestedAt: 'ASC' },
    });
  }

  /**
   * Get expired exports
   */
  async getExpiredExports(ctx: ServiceContext): Promise<DataExport[]> {
    const now = new Date();

    return this.repository
      .createQueryBuilder('de')
      .where('de.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('de.status = :status', { status: 'completed' })
      .andWhere('de.download_expires_at < :now', { now })
      .getMany();
  }

  /**
   * Mark expired exports
   */
  async markExpiredExports(ctx: ServiceContext): Promise<number> {
    const now = new Date();

    const result = await this.repository
      .createQueryBuilder()
      .update(DataExport)
      .set({ status: 'expired' })
      .where('tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('status = :status', { status: 'completed' })
      .andWhere('download_expires_at < :now', { now })
      .execute();

    return result.affected || 0;
  }

  /**
   * Get GDPR export requests
   */
  async getGdprRequests(
    ctx: ServiceContext,
    page = 1,
    limit = 20,
  ): Promise<PaginatedResult<DataExport>> {
    return this.findWithFilters(ctx, { exportType: 'gdpr_request' }, page, limit);
  }

  /**
   * Get statistics
   */
  async getStats(ctx: ServiceContext, days = 30): Promise<DataExportStats> {
    const dateFrom = new Date();
    dateFrom.setDate(dateFrom.getDate() - days);

    const [
      totalExports,
      byStatus,
      byType,
      byFormat,
      aggregates,
    ] = await Promise.all([
      this.repository
        .createQueryBuilder('de')
        .where('de.tenant_id = :tenantId', { tenantId: ctx.tenantId })
        .andWhere('de.requested_at >= :dateFrom', { dateFrom })
        .getCount(),
      this.repository
        .createQueryBuilder('de')
        .select('de.status', 'status')
        .addSelect('COUNT(*)', 'count')
        .where('de.tenant_id = :tenantId', { tenantId: ctx.tenantId })
        .andWhere('de.requested_at >= :dateFrom', { dateFrom })
        .groupBy('de.status')
        .getRawMany(),
      this.repository
        .createQueryBuilder('de')
        .select('de.export_type', 'type')
        .addSelect('COUNT(*)', 'count')
        .where('de.tenant_id = :tenantId', { tenantId: ctx.tenantId })
        .andWhere('de.requested_at >= :dateFrom', { dateFrom })
        .groupBy('de.export_type')
        .getRawMany(),
      this.repository
        .createQueryBuilder('de')
        .select('de.export_format', 'format')
        .addSelect('COUNT(*)', 'count')
        .where('de.tenant_id = :tenantId', { tenantId: ctx.tenantId })
        .andWhere('de.requested_at >= :dateFrom', { dateFrom })
        .andWhere('de.export_format IS NOT NULL')
        .groupBy('de.export_format')
        .getRawMany(),
      this.repository
        .createQueryBuilder('de')
        .select('COALESCE(SUM(de.record_count), 0)', 'totalRecords')
        .addSelect('COALESCE(SUM(de.file_size_bytes), 0)', 'totalBytes')
        .where('de.tenant_id = :tenantId', { tenantId: ctx.tenantId })
        .andWhere('de.requested_at >= :dateFrom', { dateFrom })
        .andWhere('de.status = :status', { status: 'completed' })
        .getRawOne(),
    ]);

    return {
      period: { days, from: dateFrom, to: new Date() },
      totalExports,
      byStatus: byStatus.map((r) => ({ status: r.status, count: parseInt(r.count) })),
      byType: byType.map((r) => ({ type: r.type, count: parseInt(r.count) })),
      byFormat: byFormat.map((r) => ({ format: r.format || 'unknown', count: parseInt(r.count) })),
      totalRecordsExported: parseInt(aggregates?.totalRecords || '0'),
      totalBytesExported: parseInt(aggregates?.totalBytes || '0'),
    };
  }
}
