/**
 * Report Execution Service
 * Manages report execution, tracking, and history
 *
 * @module Reports
 */

import { Repository, FindOptionsWhere, Between, LessThanOrEqual, MoreThanOrEqual } from 'typeorm';
import { ReportExecution, ExecutionStatus } from '../entities/report-execution.entity';
import { Report, ReportFormat } from '../entities/report.entity';

export interface ExecutionSearchParams {
  tenantId: string;
  reportId?: string;
  status?: ExecutionStatus;
  executedById?: string;
  isScheduled?: boolean;
  dateFrom?: Date;
  dateTo?: Date;
  limit?: number;
  offset?: number;
}

export interface CreateExecutionDto {
  reportId: string;
  format: ReportFormat;
  parameters?: Record<string, any> | null;
  filters?: Record<string, any> | null;
  isScheduled?: boolean;
}

export interface ExecuteReportParams {
  reportId: string;
  tenantId: string;
  format?: ReportFormat;
  parameters?: Record<string, any>;
  filters?: Record<string, any>;
  executedById?: string;
  isScheduled?: boolean;
}

export interface GenerateResult {
  execution: ReportExecution;
  data?: any[];
  filePath?: string;
  fileUrl?: string;
}

export class ReportExecutionService {
  constructor(
    private readonly executionRepository: Repository<ReportExecution>,
    private readonly reportRepository: Repository<Report>
  ) {}

  // ==================== CRUD Operations ====================

  async findAll(params: ExecutionSearchParams): Promise<{ data: ReportExecution[]; total: number }> {
    const {
      tenantId,
      reportId,
      status,
      executedById,
      isScheduled,
      dateFrom,
      dateTo,
      limit = 50,
      offset = 0,
    } = params;

    const where: FindOptionsWhere<ReportExecution> = { tenantId };

    if (reportId) {
      where.reportId = reportId;
    }

    if (status) {
      where.status = status;
    }

    if (executedById) {
      where.executedById = executedById;
    }

    if (isScheduled !== undefined) {
      where.isScheduled = isScheduled;
    }

    if (dateFrom && dateTo) {
      where.executedAt = Between(dateFrom, dateTo);
    } else if (dateFrom) {
      where.executedAt = MoreThanOrEqual(dateFrom);
    } else if (dateTo) {
      where.executedAt = LessThanOrEqual(dateTo);
    }

    const [data, total] = await this.executionRepository.findAndCount({
      where,
      relations: ['report'],
      take: limit,
      skip: offset,
      order: { executedAt: 'DESC' },
    });

    return { data, total };
  }

  async findOne(id: string, tenantId: string): Promise<ReportExecution | null> {
    return this.executionRepository.findOne({
      where: { id, tenantId },
      relations: ['report'],
    });
  }

  async findByReportId(
    reportId: string,
    tenantId: string,
    limit = 20
  ): Promise<ReportExecution[]> {
    return this.executionRepository.find({
      where: { reportId, tenantId },
      order: { executedAt: 'DESC' },
      take: limit,
    });
  }

  // ==================== Execution Operations ====================

  async generate(params: ExecuteReportParams): Promise<GenerateResult> {
    const {
      reportId,
      tenantId,
      format,
      parameters,
      filters,
      executedById,
      isScheduled = false,
    } = params;

    // Get report definition
    const report = await this.reportRepository.findOne({
      where: { id: reportId, tenantId },
    });

    if (!report) {
      throw new Error('Report not found');
    }

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

    const executionFormat = format || report.defaultFormat;

    // Create execution record
    const execution = this.executionRepository.create({
      tenantId,
      reportId,
      status: 'pending' as ExecutionStatus,
      format: executionFormat,
      parameters: parameters || null,
      filters: filters || null,
      isScheduled,
      executedById: executedById || null,
      executedAt: new Date(),
    });

    await this.executionRepository.save(execution);

    try {
      // Update status to running
      execution.status = 'running';
      execution.startedAt = new Date();
      await this.executionRepository.save(execution);

      // Execute the report (this would integrate with actual report generation logic)
      const result = await this.executeReportQuery(report, parameters, filters);

      // Update execution record
      execution.status = 'completed';
      execution.completedAt = new Date();
      execution.durationMs = Date.now() - execution.startedAt.getTime();
      execution.rowCount = result.rowCount;
      execution.filePath = result.filePath || null;
      execution.fileSize = result.fileSize || null;
      execution.fileUrl = result.fileUrl || null;
      execution.urlExpiresAt = result.urlExpiresAt || null;

      await this.executionRepository.save(execution);

      // Update report execution stats
      await this.reportRepository.update(
        { id: reportId },
        {
          executionCount: () => 'execution_count + 1',
          lastExecutedAt: new Date(),
        }
      );

      return {
        execution,
        data: result.data,
        filePath: result.filePath,
        fileUrl: result.fileUrl,
      };
    } catch (error) {
      // Update execution with error
      execution.status = 'failed';
      execution.completedAt = new Date();
      execution.durationMs = execution.startedAt
        ? Date.now() - execution.startedAt.getTime()
        : null;
      execution.errorMessage = error instanceof Error ? error.message : 'Unknown error';
      execution.errorStack = error instanceof Error ? error.stack || null : null;

      await this.executionRepository.save(execution);

      throw error;
    }
  }

  async cancel(id: string, tenantId: string): Promise<ReportExecution | null> {
    const execution = await this.findOne(id, tenantId);
    if (!execution) return null;

    if (execution.status !== 'pending' && execution.status !== 'running') {
      throw new Error('Can only cancel pending or running executions');
    }

    execution.status = 'cancelled';
    execution.completedAt = new Date();

    return this.executionRepository.save(execution);
  }

  async retry(
    id: string,
    tenantId: string,
    executedById?: string
  ): Promise<GenerateResult> {
    const originalExecution = await this.findOne(id, tenantId);
    if (!originalExecution) {
      throw new Error('Execution not found');
    }

    if (originalExecution.status !== 'failed') {
      throw new Error('Can only retry failed executions');
    }

    return this.generate({
      reportId: originalExecution.reportId,
      tenantId,
      format: originalExecution.format,
      parameters: originalExecution.parameters || undefined,
      filters: originalExecution.filters || undefined,
      executedById,
      isScheduled: originalExecution.isScheduled,
    });
  }

  // ==================== Export Operations ====================

  async export(
    id: string,
    tenantId: string,
    format: 'pdf' | 'excel' | 'csv'
  ): Promise<{ filePath: string; mimeType: string }> {
    const execution = await this.findOne(id, tenantId);
    if (!execution) {
      throw new Error('Execution not found');
    }

    if (execution.status !== 'completed') {
      throw new Error('Can only export completed executions');
    }

    // This would integrate with actual export logic
    const mimeTypes: Record<string, string> = {
      pdf: 'application/pdf',
      excel: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
      csv: 'text/csv',
    };

    return {
      filePath: `/exports/execution_${id}_${Date.now()}.${format}`,
      mimeType: mimeTypes[format],
    };
  }

  // ==================== History Operations ====================

  async getHistory(
    tenantId: string,
    options?: {
      reportId?: string;
      limit?: number;
      daysBack?: number;
    }
  ): Promise<ReportExecution[]> {
    const where: FindOptionsWhere<ReportExecution> = { tenantId };

    if (options?.reportId) {
      where.reportId = options.reportId;
    }

    if (options?.daysBack) {
      const dateFrom = new Date();
      dateFrom.setDate(dateFrom.getDate() - options.daysBack);
      where.executedAt = MoreThanOrEqual(dateFrom);
    }

    return this.executionRepository.find({
      where,
      relations: ['report'],
      order: { executedAt: 'DESC' },
      take: options?.limit || 50,
    });
  }

  async getStatistics(
    tenantId: string,
    reportId?: string,
    dateFrom?: Date,
    dateTo?: Date
  ): Promise<{
    total: number;
    completed: number;
    failed: number;
    cancelled: number;
    averageDuration: number;
    totalRows: number;
  }> {
    const queryBuilder = this.executionRepository
      .createQueryBuilder('e')
      .where('e.tenant_id = :tenantId', { tenantId });

    if (reportId) {
      queryBuilder.andWhere('e.report_id = :reportId', { reportId });
    }

    if (dateFrom) {
      queryBuilder.andWhere('e.executed_at >= :dateFrom', { dateFrom });
    }

    if (dateTo) {
      queryBuilder.andWhere('e.executed_at <= :dateTo', { dateTo });
    }

    const results = await queryBuilder
      .select([
        'COUNT(*) as total',
        "SUM(CASE WHEN e.status = 'completed' THEN 1 ELSE 0 END) as completed",
        "SUM(CASE WHEN e.status = 'failed' THEN 1 ELSE 0 END) as failed",
        "SUM(CASE WHEN e.status = 'cancelled' THEN 1 ELSE 0 END) as cancelled",
        "AVG(CASE WHEN e.status = 'completed' THEN e.duration_ms ELSE NULL END) as average_duration",
        'SUM(COALESCE(e.row_count, 0)) as total_rows',
      ])
      .getRawOne();

    return {
      total: parseInt(results.total || '0', 10),
      completed: parseInt(results.completed || '0', 10),
      failed: parseInt(results.failed || '0', 10),
      cancelled: parseInt(results.cancelled || '0', 10),
      averageDuration: parseFloat(results.average_duration || '0'),
      totalRows: parseInt(results.total_rows || '0', 10),
    };
  }

  async cleanupOldExecutions(
    tenantId: string,
    daysToKeep = 90
  ): Promise<number> {
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);

    const result = await this.executionRepository.delete({
      tenantId,
      executedAt: LessThanOrEqual(cutoffDate),
    });

    return result.affected || 0;
  }

  // ==================== Private Methods ====================

  private async executeReportQuery(
    report: Report,
    parameters?: Record<string, any>,
    filters?: Record<string, any>
  ): Promise<{
    data: any[];
    rowCount: number;
    filePath?: string;
    fileSize?: number;
    fileUrl?: string;
    urlExpiresAt?: Date;
  }> {
    // This is a placeholder for actual report execution logic
    // In a real implementation, this would:
    // 1. Parse the report query configuration
    // 2. Apply parameters and filters
    // 3. Execute the query against the database
    // 4. Generate the output file if needed
    // 5. Return the results

    // For now, return placeholder data
    return {
      data: [],
      rowCount: 0,
    };
  }
}
