/**
 * Report Definition Service
 * Manages report definitions CRUD and related operations
 *
 * @module Reports
 */

import { Repository, FindOptionsWhere, ILike, IsNull } from 'typeorm';
import {
  Report,
  ReportType,
  ReportFormat,
  ReportFrequency,
} from '../entities/report.entity';

export interface ReportSearchParams {
  tenantId: string;
  search?: string;
  reportType?: ReportType;
  isActive?: boolean;
  isScheduled?: boolean;
  limit?: number;
  offset?: number;
}

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

export interface UpdateReportDto extends Partial<CreateReportDto> {}

export interface GenerateReportParams {
  reportId: string;
  tenantId: string;
  format?: ReportFormat;
  parameters?: Record<string, any>;
  filters?: Record<string, any>;
  userId?: string;
}

export interface ExportFormat {
  format: 'pdf' | 'excel' | 'csv';
  options?: Record<string, any>;
}

export class ReportDefinitionService {
  constructor(private readonly reportRepository: Repository<Report>) {}

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

  async findAll(params: ReportSearchParams): Promise<{ data: Report[]; total: number }> {
    const {
      tenantId,
      search,
      reportType,
      isActive,
      isScheduled,
      limit = 50,
      offset = 0,
    } = params;

    const where: FindOptionsWhere<Report>[] = [];
    const baseWhere: FindOptionsWhere<Report> = {
      tenantId,
      deletedAt: IsNull(),
    };

    if (reportType) {
      baseWhere.reportType = reportType;
    }

    if (isActive !== undefined) {
      baseWhere.isActive = isActive;
    }

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

    if (search) {
      where.push(
        { ...baseWhere, name: ILike(`%${search}%`) },
        { ...baseWhere, code: ILike(`%${search}%`) },
        { ...baseWhere, description: ILike(`%${search}%`) }
      );
    } else {
      where.push(baseWhere);
    }

    const [data, total] = await this.reportRepository.findAndCount({
      where,
      take: limit,
      skip: offset,
      order: { name: 'ASC' },
    });

    return { data, total };
  }

  async findOne(id: string, tenantId: string): Promise<Report | null> {
    return this.reportRepository.findOne({
      where: { id, tenantId, deletedAt: IsNull() },
    });
  }

  async findByCode(code: string, tenantId: string): Promise<Report | null> {
    return this.reportRepository.findOne({
      where: { code, tenantId, deletedAt: IsNull() },
    });
  }

  async findByType(
    tenantId: string,
    reportType: ReportType
  ): Promise<Report[]> {
    return this.reportRepository.find({
      where: { tenantId, reportType, isActive: true, deletedAt: IsNull() },
      order: { name: 'ASC' },
    });
  }

  async create(
    tenantId: string,
    dto: CreateReportDto,
    createdById?: string
  ): Promise<Report> {
    // Check for existing code
    const existingCode = await this.findByCode(dto.code, tenantId);
    if (existingCode) {
      throw new Error('A report with this code already exists');
    }

    const report = this.reportRepository.create({
      ...dto,
      tenantId,
      createdById,
    });

    return this.reportRepository.save(report);
  }

  async update(
    id: string,
    tenantId: string,
    dto: UpdateReportDto,
    updatedById?: string
  ): Promise<Report | null> {
    const report = await this.findOne(id, tenantId);
    if (!report) return null;

    // If changing code, check for duplicates
    if (dto.code && dto.code !== report.code) {
      const existing = await this.findByCode(dto.code, tenantId);
      if (existing) {
        throw new Error('A report with this code already exists');
      }
    }

    // Cannot modify system reports
    if (report.isSystem && dto.isSystem === false) {
      throw new Error('Cannot modify system report flag');
    }

    Object.assign(report, {
      ...dto,
      updatedById,
    });

    return this.reportRepository.save(report);
  }

  async delete(id: string, tenantId: string, deletedById?: string): Promise<boolean> {
    const report = await this.findOne(id, tenantId);
    if (!report) return false;

    // Cannot delete system reports
    if (report.isSystem) {
      throw new Error('Cannot delete system reports');
    }

    report.deletedAt = new Date();
    report.deletedById = deletedById || null;
    await this.reportRepository.save(report);

    return true;
  }

  // ==================== Report Operations ====================

  async getScheduledReports(tenantId: string): Promise<Report[]> {
    return this.reportRepository.find({
      where: {
        tenantId,
        isScheduled: true,
        isActive: true,
        deletedAt: IsNull(),
      },
      order: { name: 'ASC' },
    });
  }

  async getSystemReports(tenantId: string): Promise<Report[]> {
    return this.reportRepository.find({
      where: {
        tenantId,
        isSystem: true,
        isActive: true,
        deletedAt: IsNull(),
      },
      order: { name: 'ASC' },
    });
  }

  async updateExecutionStats(
    id: string,
    tenantId: string
  ): Promise<void> {
    await this.reportRepository.update(
      { id, tenantId },
      {
        executionCount: () => 'execution_count + 1',
        lastExecutedAt: new Date(),
      }
    );
  }

  async toggleActive(
    id: string,
    tenantId: string,
    updatedById?: string
  ): Promise<Report | null> {
    const report = await this.findOne(id, tenantId);
    if (!report) return null;

    report.isActive = !report.isActive;
    report.updatedById = updatedById || null;

    return this.reportRepository.save(report);
  }

  async clone(
    id: string,
    tenantId: string,
    newCode: string,
    newName: string,
    createdById?: string
  ): Promise<Report> {
    const source = await this.findOne(id, tenantId);
    if (!source) {
      throw new Error('Source report not found');
    }

    // Check new code doesn't exist
    const existingCode = await this.findByCode(newCode, tenantId);
    if (existingCode) {
      throw new Error('A report with this code already exists');
    }

    const cloned = this.reportRepository.create({
      tenantId,
      code: newCode,
      name: newName,
      description: source.description,
      reportType: source.reportType,
      defaultFormat: source.defaultFormat,
      availableFormats: source.availableFormats,
      query: source.query,
      parameters: source.parameters,
      columns: source.columns,
      grouping: source.grouping,
      sorting: source.sorting,
      filters: source.filters,
      templatePath: source.templatePath,
      isScheduled: false,
      frequency: null,
      scheduleConfig: null,
      distributionList: null,
      isActive: true,
      isSystem: false,
      createdById,
    });

    return this.reportRepository.save(cloned);
  }

  // ==================== Export Methods ====================

  async export(
    id: string,
    tenantId: string,
    exportFormat: ExportFormat
  ): Promise<{ filePath: string; mimeType: string }> {
    const report = await this.findOne(id, tenantId);
    if (!report) {
      throw new Error('Report not found');
    }

    // Validate format is available
    if (!report.availableFormats.includes(exportFormat.format)) {
      throw new Error(`Format ${exportFormat.format} is not available for this report`);
    }

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

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

  // ==================== Schedule Methods ====================

  async schedule(
    id: string,
    tenantId: string,
    cronExpression: string,
    options?: {
      frequency?: ReportFrequency;
      distributionList?: string[];
      scheduleConfig?: Record<string, any>;
    },
    updatedById?: string
  ): Promise<Report | null> {
    const report = await this.findOne(id, tenantId);
    if (!report) return null;

    report.isScheduled = true;
    report.frequency = options?.frequency || 'daily';
    report.scheduleConfig = {
      ...(report.scheduleConfig || {}),
      cronExpression,
      ...(options?.scheduleConfig || {}),
    };
    report.distributionList = options?.distributionList || report.distributionList;
    report.updatedById = updatedById || null;

    return this.reportRepository.save(report);
  }

  async unschedule(
    id: string,
    tenantId: string,
    updatedById?: string
  ): Promise<Report | null> {
    const report = await this.findOne(id, tenantId);
    if (!report) return null;

    report.isScheduled = false;
    report.updatedById = updatedById || null;

    return this.reportRepository.save(report);
  }

  // ==================== History Methods ====================

  async getHistory(
    tenantId: string,
    options?: {
      reportType?: ReportType;
      limit?: number;
    }
  ): Promise<Report[]> {
    const where: FindOptionsWhere<Report> = {
      tenantId,
      isActive: true,
      deletedAt: IsNull(),
    };

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

    return this.reportRepository.find({
      where,
      order: { lastExecutedAt: 'DESC' },
      take: options?.limit || 20,
    });
  }
}
