/**
 * ReportService - Gestión de Reportes
 *
 * Administra definiciones de reportes, ejecuciones y distribución.
 *
 * @module Reports
 */

import { DataSource, Repository, IsNull } from 'typeorm';
import { Report, ReportType, ReportFormat, ReportFrequency } from '../entities/report.entity';
import { ReportExecution, ExecutionStatus } from '../entities/report-execution.entity';

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

interface PaginatedResult<T> {
  data: T[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

export interface CreateReportDto {
  code: string;
  name: string;
  description?: string;
  reportType: ReportType;
  defaultFormat?: ReportFormat;
  availableFormats?: ReportFormat[];
  query?: Record<string, any>;
  parameters?: Record<string, any>;
  columns?: Record<string, any>[];
  grouping?: Record<string, any>;
  sorting?: Record<string, any>[];
  filters?: Record<string, any>;
  templatePath?: string;
  isScheduled?: boolean;
  frequency?: ReportFrequency;
  scheduleConfig?: Record<string, any>;
  distributionList?: string[];
}

export interface UpdateReportDto extends Partial<CreateReportDto> {
  isActive?: boolean;
}

export interface ReportFilters {
  reportType?: ReportType;
  isActive?: boolean;
  isScheduled?: boolean;
  search?: string;
}

export interface ExecuteReportDto {
  format?: ReportFormat;
  parameters?: Record<string, any>;
  filters?: Record<string, any>;
  distribute?: boolean;
}

export class ReportService {
  private repository: Repository<Report>;
  private executionRepository: Repository<ReportExecution>;

  constructor(dataSource: DataSource) {
    this.repository = dataSource.getRepository(Report);
    this.executionRepository = dataSource.getRepository(ReportExecution);
  }

  /**
   * Buscar reporte por ID
   */
  async findById(ctx: ServiceContext, id: string): Promise<Report | null> {
    return this.repository.findOne({
      where: { id, tenantId: ctx.tenantId, deletedAt: IsNull() },
    });
  }

  /**
   * Crear reporte
   */
  async create(ctx: ServiceContext, data: CreateReportDto & { isActive: boolean; isSystem: boolean; executionCount: number }): Promise<Report> {
    const report = this.repository.create({
      ...data,
      tenantId: ctx.tenantId,
    });
    return this.repository.save(report);
  }

  /**
   * Actualizar reporte
   */
  async update(ctx: ServiceContext, id: string, data: Partial<UpdateReportDto>): Promise<Report | null> {
    await this.repository.update(
      { id, tenantId: ctx.tenantId, deletedAt: IsNull() },
      data
    );
    return this.findById(ctx, id);
  }

  /**
   * Soft delete reporte
   */
  async softDelete(ctx: ServiceContext, id: string): Promise<boolean> {
    const result = await this.repository.update(
      { id, tenantId: ctx.tenantId, deletedAt: IsNull() },
      { deletedAt: new Date() }
    );
    return result.affected! > 0;
  }

  /**
   * Buscar reportes con filtros
   */
  async findWithFilters(
    ctx: ServiceContext,
    filters: ReportFilters,
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<Report>> {
    const qb = this.repository
      .createQueryBuilder('r')
      .where('r.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('r.deleted_at IS NULL');

    if (filters.reportType) {
      qb.andWhere('r.report_type = :reportType', { reportType: filters.reportType });
    }
    if (filters.isActive !== undefined) {
      qb.andWhere('r.is_active = :isActive', { isActive: filters.isActive });
    }
    if (filters.isScheduled !== undefined) {
      qb.andWhere('r.is_scheduled = :isScheduled', { isScheduled: filters.isScheduled });
    }
    if (filters.search) {
      qb.andWhere('(r.name ILIKE :search OR r.code ILIKE :search OR r.description ILIKE :search)', {
        search: `%${filters.search}%`,
      });
    }

    const skip = (page - 1) * limit;
    qb.orderBy('r.name', 'ASC').skip(skip).take(limit);

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

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

  /**
   * Buscar reporte por código
   */
  async findByCode(ctx: ServiceContext, code: string): Promise<Report | null> {
    return this.repository.findOne({
      where: {
        tenantId: ctx.tenantId,
        code,
        deletedAt: null,
      } as any,
    });
  }

  /**
   * Obtener reportes por tipo
   */
  async findByType(ctx: ServiceContext, reportType: ReportType): Promise<Report[]> {
    return this.repository.find({
      where: {
        tenantId: ctx.tenantId,
        reportType,
        isActive: true,
        deletedAt: null,
      } as any,
      order: { name: 'ASC' },
    });
  }

  /**
   * Obtener reportes programados
   */
  async findScheduled(ctx: ServiceContext): Promise<Report[]> {
    return this.repository.find({
      where: {
        tenantId: ctx.tenantId,
        isScheduled: true,
        isActive: true,
        deletedAt: null,
      } as any,
      order: { name: 'ASC' },
    });
  }

  /**
   * Crear reporte
   */
  async createReport(ctx: ServiceContext, data: CreateReportDto): Promise<Report> {
    const existing = await this.findByCode(ctx, data.code);
    if (existing) {
      throw new Error(`Report with code ${data.code} already exists`);
    }

    return this.create(ctx, {
      ...data,
      isActive: true,
      isSystem: false,
      executionCount: 0,
    });
  }

  /**
   * Ejecutar reporte
   */
  async executeReport(
    ctx: ServiceContext,
    reportId: string,
    options: ExecuteReportDto
  ): Promise<ReportExecution> {
    const report = await this.findById(ctx, reportId);
    if (!report) {
      throw new Error('Report not found');
    }

    if (!report.isActive) {
      throw new Error('Report is not active');
    }

    // Crear registro de ejecución
    const execution = this.executionRepository.create({
      tenantId: ctx.tenantId,
      reportId,
      status: 'pending' as ExecutionStatus,
      format: options.format || report.defaultFormat,
      parameters: options.parameters || report.parameters,
      filters: options.filters || report.filters,
      isScheduled: false,
      executedById: ctx.userId,
    });

    const savedExecution = await this.executionRepository.save(execution);

    // En una implementación real, aquí se ejecutaría el reporte de forma asíncrona
    // Por ahora, simulamos el proceso
    try {
      await this.processReportExecution(ctx, savedExecution, report, options);
    } catch (error) {
      await this.markExecutionFailed(savedExecution.id, error as Error);
      throw error;
    }

    // Actualizar contador de ejecuciones
    await this.repository.update(
      { id: reportId } as any,
      {
        executionCount: () => 'execution_count + 1',
        lastExecutedAt: new Date(),
      } as any
    );

    return this.executionRepository.findOne({
      where: { id: savedExecution.id } as any,
    }) as Promise<ReportExecution>;
  }

  /**
   * Procesar ejecución del reporte
   */
  private async processReportExecution(
    _ctx: ServiceContext,
    execution: ReportExecution,
    _report: Report,
    _options: ExecuteReportDto
  ): Promise<void> {
    const startTime = Date.now();

    await this.executionRepository.update(
      { id: execution.id } as any,
      { status: 'running', startedAt: new Date() } as any
    );

    // Aquí iría la lógica real de generación del reporte
    // Por ahora simulamos una ejecución exitosa
    const duration = Date.now() - startTime;

    await this.executionRepository.update(
      { id: execution.id } as any,
      {
        status: 'completed',
        completedAt: new Date(),
        durationMs: duration,
        rowCount: 0, // Se calcularía del resultado real
      } as any
    );
  }

  /**
   * Marcar ejecución como fallida
   */
  private async markExecutionFailed(executionId: string, error: Error): Promise<void> {
    await this.executionRepository.update(
      { id: executionId } as any,
      {
        status: 'failed',
        completedAt: new Date(),
        errorMessage: error.message,
        errorStack: error.stack,
      } as any
    );
  }

  /**
   * Obtener historial de ejecuciones
   */
  async getExecutionHistory(
    ctx: ServiceContext,
    reportId: string,
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<ReportExecution>> {
    const skip = (page - 1) * limit;

    const [data, total] = await this.executionRepository.findAndCount({
      where: {
        tenantId: ctx.tenantId,
        reportId,
      } as any,
      order: { executedAt: 'DESC' },
      skip,
      take: limit,
    });

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

  /**
   * Obtener última ejecución
   */
  async getLastExecution(ctx: ServiceContext, reportId: string): Promise<ReportExecution | null> {
    return this.executionRepository.findOne({
      where: {
        tenantId: ctx.tenantId,
        reportId,
      } as any,
      order: { executedAt: 'DESC' },
    });
  }

  /**
   * Obtener ejecución por ID
   */
  async getExecution(ctx: ServiceContext, executionId: string): Promise<ReportExecution | null> {
    return this.executionRepository.findOne({
      where: {
        tenantId: ctx.tenantId,
        id: executionId,
      } as any,
    });
  }

  /**
   * Estadísticas de reportes
   */
  async getStats(ctx: ServiceContext): Promise<ReportStats> {
    const reports = await this.repository.find({
      where: {
        tenantId: ctx.tenantId,
        deletedAt: null,
      } as any,
    });

    const byType = new Map<ReportType, number>();
    let activeCount = 0;
    let scheduledCount = 0;
    let totalExecutions = 0;

    reports.forEach((r) => {
      byType.set(r.reportType, (byType.get(r.reportType) || 0) + 1);
      if (r.isActive) activeCount++;
      if (r.isScheduled) scheduledCount++;
      totalExecutions += r.executionCount;
    });

    return {
      totalReports: reports.length,
      activeReports: activeCount,
      scheduledReports: scheduledCount,
      totalExecutions,
      byType: Array.from(byType.entries()).map(([type, count]) => ({ type, count })),
    };
  }
}

export interface ReportStats {
  totalReports: number;
  activeReports: number;
  scheduledReports: number;
  totalExecutions: number;
  byType: { type: ReportType; count: number }[];
}
