import { Injectable, Logger, BadRequestException, NotFoundException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Cron, CronExpression } from '@nestjs/schedule';
import { PrismaService } from '@core/database/prisma/prisma.service';
import { AdminAnalyticsService } from './admin-analytics.service';
import { AdminConfigService } from './admin-config.service';
import { AdminInstancesExportDto } from './dto/approval.dto';
import {
  AdminExportResponse,
  AdminExportStatusResponse,
  AdminInstanceItem,
} from './dto/approval-response.dto';
import { ApprovalAdminExport, Prisma } from '@prisma/client';
import {
  S3Client,
  PutObjectCommand,
  DeleteObjectCommand,
  GetObjectCommand,
  HeadBucketCommand,
  CreateBucketCommand,
} from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import * as XLSX from 'xlsx';

const EXPORT_PREFIX = 'approval-exports';
const MAX_EXPORT_ROWS = 50000;
const SIGNED_URL_EXPIRES_IN = 60 * 60;

@Injectable()
export class AdminExportService {
  private readonly logger = new Logger(AdminExportService.name);
  private readonly s3Client: S3Client;
  private readonly bucket: string;
  private readonly isS3Configured: boolean;

  constructor(
    private readonly prisma: PrismaService,
    private readonly configService: ConfigService,
    private readonly adminAnalyticsService: AdminAnalyticsService,
    private readonly adminConfigService: AdminConfigService,
  ) {
    const s3Config = this.configService.get('s3');
    this.bucket = this.normalizeBucketName(s3Config?.bucket || 'ffoa-attachments');
    this.isS3Configured = Boolean(
      s3Config?.endpoint && s3Config?.accessKeyId && s3Config?.secretAccessKey,
    );

    this.s3Client = new S3Client({
      region: s3Config?.region || 'us-east-1',
      endpoint: s3Config?.endpoint,
      credentials: s3Config?.accessKeyId && s3Config?.secretAccessKey
        ? {
            accessKeyId: s3Config.accessKeyId,
            secretAccessKey: s3Config.secretAccessKey,
          }
        : undefined,
      forcePathStyle: true,
    });
  }

  private normalizeBucketName(bucket: string): string {
    const normalized = bucket.toLowerCase().replace(/_/g, '-');
    if (normalized !== bucket) {
      this.logger.warn(`Export bucket "${bucket}" normalized to "${normalized}".`);
    }
    return normalized;
  }

  async createExportTask(
    dto: AdminInstancesExportDto,
    userId: string,
  ): Promise<AdminExportResponse> {
    if (!this.isS3Configured) {
      throw new BadRequestException('导出存储未配置');
    }

    const task = await this.prisma.approvalAdminExport.create({
      data: {
        requestedBy: userId,
        format: dto.format || 'xlsx',
        filters: dto as unknown as Prisma.InputJsonValue,
      },
    });

    void this.processExportTask(task).catch((error) => {
      this.logger.error(`Export task ${task.id} failed`, error);
    });

    return { taskId: task.id };
  }

  async getExportStatus(
    taskId: string,
  ): Promise<AdminExportStatusResponse> {
    const task = await this.prisma.approvalAdminExport.findUnique({
      where: { id: taskId },
    });

    if (!task) {
      throw new NotFoundException('导出任务不存在');
    }

    const downloadUrl = task.status === 'SUCCESS' && task.fileKey
      ? await this.getSignedDownloadUrl(task.fileKey)
      : undefined;

    return {
      id: task.id,
      status: task.status,
      format: task.format,
      fileName: task.fileName || undefined,
      fileSize: task.fileSize ? Number(task.fileSize) : undefined,
      contentType: task.contentType || undefined,
      downloadUrl,
      createdAt: task.createdAt.toISOString(),
      completedAt: task.completedAt?.toISOString(),
      expiresAt: task.expiresAt?.toISOString(),
    };
  }

  async listExports(userId: string, limit = 20): Promise<AdminExportStatusResponse[]> {
    const tasks = await this.prisma.approvalAdminExport.findMany({
      where: { requestedBy: userId },
      orderBy: { createdAt: 'desc' },
      take: limit,
    });

    const results: AdminExportStatusResponse[] = [];
    for (const task of tasks) {
      const downloadUrl = task.status === 'SUCCESS' && task.fileKey
        ? await this.getSignedDownloadUrl(task.fileKey)
        : undefined;

      results.push({
        id: task.id,
        status: task.status,
        format: task.format,
        fileName: task.fileName || undefined,
        fileSize: task.fileSize ? Number(task.fileSize) : undefined,
        contentType: task.contentType || undefined,
        downloadUrl,
        createdAt: task.createdAt.toISOString(),
        completedAt: task.completedAt?.toISOString(),
        expiresAt: task.expiresAt?.toISOString(),
      });
    }

    return results;
  }

  @Cron(CronExpression.EVERY_MINUTE)
  async handlePendingExports() {
    const pending = await this.prisma.approvalAdminExport.findMany({
      where: { status: 'PENDING' },
      orderBy: { createdAt: 'asc' },
      take: 3,
    });

    for (const task of pending) {
      try {
        await this.processExportTask(task);
      } catch (error) {
        this.logger.error(`Export task ${task.id} failed`, error);
      }
    }
  }

  @Cron(CronExpression.EVERY_DAY_AT_3AM)
  async cleanupExpiredExports() {
    const now = new Date();
    const expired = await this.prisma.approvalAdminExport.findMany({
      where: {
        status: 'SUCCESS',
        expiresAt: { lte: now },
      },
    });

    for (const task of expired) {
      if (task.fileKey) {
        await this.deleteObject(task.fileKey);
      }
      await this.prisma.approvalAdminExport.update({
        where: { id: task.id },
        data: { status: 'EXPIRED' },
      });
    }
  }

  private async processExportTask(task: ApprovalAdminExport) {
    if (!this.isS3Configured) {
      throw new Error('导出存储未配置');
    }
    await this.prisma.approvalAdminExport.update({
      where: { id: task.id },
      data: { status: 'PROCESSING', startedAt: new Date() },
    });

    try {
      const filters = task.filters as AdminInstancesExportDto;
      const rows = await this.adminAnalyticsService.getAdminInstancesForExport(filters);

      if (rows.length > MAX_EXPORT_ROWS) {
        throw new Error(`导出记录超过上限 (${MAX_EXPORT_ROWS})`);
      }

      const { buffer, contentType, fileName } = this.buildExportFile(rows, task.format);
      const fileKey = `${EXPORT_PREFIX}/${task.id}/${fileName}`;
      await this.putObject(fileKey, buffer, contentType);

      const retention = await this.adminConfigService.getSettings();
      const expiresAt = new Date(Date.now() + retention.exportRetentionDays * 24 * 60 * 60 * 1000);

      await this.prisma.approvalAdminExport.update({
        where: { id: task.id },
        data: {
          status: 'SUCCESS',
          fileKey,
          fileName,
          fileSize: BigInt(buffer.length),
          contentType,
          totalRows: rows.length,
          completedAt: new Date(),
          expiresAt,
        },
      });
    } catch (error) {
      await this.prisma.approvalAdminExport.update({
        where: { id: task.id },
        data: {
          status: 'FAILED',
          errorMessage: error instanceof Error ? error.message : '导出失败',
          completedAt: new Date(),
        },
      });
      throw error;
    }
  }

  private buildExportFile(rows: AdminInstanceItem[], format: string) {
    const columns = [
      { key: 'businessInstanceId', label: '实例ID' },
      { key: 'instanceId', label: '审批实例ID' },
      { key: 'formName', label: '表单名称' },
      { key: 'formKey', label: '表单Key' },
      { key: 'submitterName', label: '发起人' },
      { key: 'status', label: '状态' },
      { key: 'currentNode', label: '当前节点' },
      { key: 'submittedAt', label: '提交时间' },
      { key: 'durationMs', label: '耗时(毫秒)' },
      { key: 'approvalRequired', label: '是否审批' },
    ];

    const data = rows.map((row) => ({
      businessInstanceId: row.businessInstanceId,
      instanceId: row.instanceId || '',
      formName: row.formName || '',
      formKey: row.formKey || '',
      submitterName: row.submitter.name || '',
      status: row.status,
      currentNode: row.currentNode || '',
      submittedAt: row.submittedAt,
      durationMs: row.durationMs ?? '',
      approvalRequired: row.approvalRequired ? '是' : '否',
    }));

    if (format === 'csv') {
      const header = columns.map((col) => col.label).join(',');
      const lines = data.map((row) =>
        columns
          .map((col) => {
            const value = row[col.key as keyof typeof row];
            if (value === null || value === undefined) return '';
            const escaped = String(value).replace(/"/g, '""');
            return `"${escaped}"`;
          })
          .join(','),
      );

      return {
        buffer: Buffer.from([header, ...lines].join('\n'), 'utf-8'),
        contentType: 'text/csv',
        fileName: `approval-admin-export-${Date.now()}.csv`,
      };
    }

    const worksheet = XLSX.utils.aoa_to_sheet([
      columns.map((col) => col.label),
      ...data.map((row) => columns.map((col) => row[col.key as keyof typeof row])),
    ]);
    const workbook = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(workbook, worksheet, 'admin_exports');
    const buffer = XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' }) as Buffer;

    return {
      buffer,
      contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
      fileName: `approval-admin-export-${Date.now()}.xlsx`,
    };
  }

  private async putObject(key: string, buffer: Buffer, contentType: string) {
    await this.ensureBucketExists();
    await this.s3Client.send(
      new PutObjectCommand({
        Bucket: this.bucket,
        Key: key,
        Body: buffer,
        ContentType: contentType,
      }),
    );
  }

  private async ensureBucketExists() {
    try {
      await this.s3Client.send(
        new HeadBucketCommand({ Bucket: this.bucket }),
      );
    } catch (error) {
      const status = (error as { $metadata?: { httpStatusCode?: number } })?.$metadata?.httpStatusCode;
      const name = (error as { name?: string })?.name;
      if (status === 404 || name === 'NotFound' || name === 'NoSuchBucket') {
        this.logger.warn(`Export bucket "${this.bucket}" not found, creating...`);
        await this.s3Client.send(
          new CreateBucketCommand({ Bucket: this.bucket }),
        );
        return;
      }
      throw error;
    }
  }

  private async deleteObject(key: string) {
    await this.s3Client.send(
      new DeleteObjectCommand({
        Bucket: this.bucket,
        Key: key,
      }),
    );
  }

  private async getSignedDownloadUrl(key: string): Promise<string> {
    return getSignedUrl(
      this.s3Client,
      new GetObjectCommand({
        Bucket: this.bucket,
        Key: key,
      }),
      { expiresIn: SIGNED_URL_EXPIRES_IN },
    );
  }
}
