import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { customAlphabet } from 'nanoid-cjs';
import { db, ObjectType, Prisma, Resource } from '@nice/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { ResourceService } from '@server/models/resource/resource.service';
import * as fs from 'fs'
import * as path from 'path'
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import { BaseService } from '../base/base.service';
dayjs.extend(utc);
dayjs.extend(timezone);
export interface ShareCode {
  id: string;
  code: string;
  fileId: string;
  createdAt: Date;
  expiresAt: Date;
  isUsed: boolean;
  fileName?: string | null;
  canUseTimes: number | null;
}
export interface GenerateShareCodeResponse {
  code: string;
  expiresAt: Date;
  canUseTimes: number;
}

interface ResourceMeta {
  filename: string;
  filetype: string;
  filesize: string;
}

@Injectable()
export class ShareCodeService extends BaseService<Prisma.ShareCodeDelegate> {
  private readonly logger = new Logger(ShareCodeService.name);
  // 生成8位分享码，使用易读的字符
  private readonly generateCode = customAlphabet(
    '23456789ABCDEFGHJKLMNPQRSTUVWXYZ',
    8,
  );

  constructor(private readonly resourceService: ResourceService) {
    super(db, ObjectType.SHARE_CODE, false);
   }

  async generateShareCode(
    fileId: string,
    expiresAt: Date,
    canUseTimes: number,
    fileName?: string,
  ): Promise<GenerateShareCodeResponse> {
    try {
      // 检查文件是否存在
      const resource = await this.resourceService.findUnique({
        where: { fileId },
      });
      this.logger.log('完整 resource:', resource);
      if (!resource) {
        throw new NotFoundException('文件不存在');
      }
      const { filename } = resource.meta as any as ResourceMeta
      // 生成分享码
      const code = this.generateCode();
      // 查找是否已有分享码记录
      const existingShareCode = await super.findUnique({
        where: { fileId },
      });

      if (existingShareCode) {
        // 更新现有记录，但保留原有文件名
        await super.update({
          where: { fileId },
          data: {
            code,
            expiresAt,
            canUseTimes,
            isUsed: false,
            fileName: filename|| "downloaded_file",
          },
        });
      } else {
        // 创建新记录
        await super.create({
          data: {
            code,
            fileId,
            expiresAt,
            canUseTimes,
            isUsed: false,
            fileName: filename || "downloaded_file",
          },
        });
      }
      this.logger.log(`Generated share code ${code} for file ${fileId} canUseTimes: ${canUseTimes}`);
      return {
        code,
        expiresAt,
        canUseTimes,
      };
    } catch (error) {
      this.logger.error('Failed to generate share code', error);
      throw error;
    }
  }

  async validateAndUseCode(code: string): Promise<ShareCode | null> {
    try {
      this.logger.log(`尝试验证分享码: ${code}`);

      // 查找有效的分享码
      const shareCode = await db.shareCode.findFirst({
        where: {
          code,
          isUsed: false,
          expiresAt: { gt: dayjs().tz('Asia/Shanghai').toDate() },
        },
      });
      if (shareCode.canUseTimes <= 0) {
        this.logger.log('分享码已使用次数超过限制');
        return null;
      }
      //更新已使用次数
      await db.shareCode.update({
        where: { id: shareCode.id },
        data: { canUseTimes: shareCode.canUseTimes - 1 },
      });
      this.logger.log('查询结果:', shareCode);

      if (!shareCode) {
        this.logger.log('分享码无效或已过期');
        return null;
      }
      // 记录使用日志
      this.logger.log(`Share code ${code} used for file ${shareCode.fileId}`);
      // 返回完整的分享码信息，包括文件名
      return shareCode;
    } catch (error) {
      this.logger.error('Failed to validate share code', error);
      return null;
    }
  }

  // 每天清理过期的分享码
  //@Cron('*/30 * * * * *')
  @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
  async cleanupExpiredShareCodes() {
    try {
      const shareCodes = await db.shareCode.findMany({
        where: {
          OR: [
            { expiresAt: { lt: dayjs().tz('Asia/Shanghai').toDate() } },
            { isUsed: true },
            { canUseTimes: { lte: 0 } }
          ],
        }
      })
      this.logger.log('需要清理的分享码:', shareCodes);
      shareCodes.forEach(code => {
        this.cleanupUploadFolder(code.fileId);
      })
      const result = await db.shareCode.deleteMany({
        where: {
          fileId: {
            in: shareCodes.map(code => code.fileId)
          }
        },
      });
      const deleteResource = await this.resourceService.deleteMany({
        where: {
          fileId: {
            in: shareCodes.map(code => code.fileId)
          }
        }
      })
      this.logger.log(`Cleaned up ${result.count} ${deleteResource.count} expired share codes`);
    } catch (error) {
      this.logger.error('Failed to cleanup expired share codes', error);
    }
  }

  async cleanupUploadFolder(file?: string) {
    //const uploadDir = path.join(__dirname, '../../../uploads');
    const uploadDir = path.join('/data/uploads/', file || '');
    this.logger.log('uploadDir:', uploadDir);
    try {
      if (!fs.existsSync(uploadDir)) {
        this.logger.warn(`Upload directory does not exist: ${uploadDir}`);
        return;
      }
      // 递归删除文件夹及其内容
      this.deleteFolderRecursive(uploadDir);
      this.logger.log(`Cleaned up upload folder: ${uploadDir}`);
    } catch (error) {
      this.logger.error('读取上传目录失败:', error);
      return;
    }
  }
  private deleteFolderRecursive(dirPath: string) {
    if (fs.existsSync(dirPath)) {
      fs.readdirSync(dirPath).forEach((file) => {
        const filePath = path.join(dirPath, file);
        if (fs.statSync(filePath).isDirectory()) {
          // 递归删除子目录
          this.deleteFolderRecursive(filePath);
        } else {
          // 删除文件
          fs.unlinkSync(filePath);
          this.logger.log(`Deleted file: ${filePath}`);
        }
      });
      // 删除空文件夹
      fs.rmdirSync(dirPath);
      this.logger.log(`Deleted folder: ${dirPath}`);
    }
  }
  // 获取分享码信息
  async getShareCodeInfo(code: string): Promise<ShareCode | null> {
    try {
      return await db.shareCode.findFirst({
        where: { code },
      });
    } catch (error) {
      this.logger.error('Failed to get share code info', error);
      return null;
    }
  }

  // 检查文件是否已经生成过分享码
  async hasActiveShareCode(fileId: string): Promise<boolean> {
    try {
      const activeCode = await db.shareCode.findFirst({
        where: {
          fileId,
          isUsed: false,
          expiresAt: { gt: dayjs().tz('Asia/Shanghai').toDate() },
        },
      });

      return !!activeCode;
    } catch (error) {
      this.logger.error('Failed to check active share code', error);
      return false;
    }
  }

  // 获取文件的所有分享记录
  async getFileShareHistory(fileId: string) {
    try {
      return await db.shareCode.findMany({
        where: { fileId },
        orderBy: { createdAt: 'desc' },
      });
    } catch (error) {
      this.logger.error('Failed to get file share history', error);
      return [];
    }
  }

  // 根据分享码获取文件
  async getFileByShareCode(code: string) {
    this.logger.log('收到验证分享码请求，code:', code);
    const shareCode = await this.validateAndUseCode(code);
    this.logger.log('验证分享码结果:', shareCode);

    if (!shareCode) {
      throw new NotFoundException('分享码无效或已过期');
    }

    // 获取文件信息
    const resource = await this.resourceService.findUnique({
      where: { fileId: shareCode.fileId },
    });
    this.logger.log('获取到的资源信息:', resource);
    const { filename } = resource.meta as any as ResourceMeta
    const fileUrl = resource?.url
    if (!resource) {
      throw new NotFoundException('文件不存在');
    }

    // 直接返回正确的数据结构
    const response = {
      fileId: shareCode.fileId,
      fileName: filename || 'downloaded_file',
      code: shareCode.code,
      expiresAt: shareCode.expiresAt,
      url: fileUrl,
      canUseTimes: shareCode.canUseTimes - 1,
    };

    this.logger.log('返回给前端的数据:', response);  // 添加日志
    return response;
  }

  // 根据文件ID生成分享码
  async generateShareCodeByFileId(fileId: string, expiresAt: Date, canUseTimes: number) {
    try {
      this.logger.log('收到生成分享码请求，fileId:', fileId);
      const result = await this.generateShareCode(fileId, expiresAt, canUseTimes);
      this.logger.log('生成分享码结果:', result);
      return result;
    } catch (error) {
      this.logger.error('生成分享码错误:', error);
      return error
    }
  }

  async getShareCodesWithResources(args: {
    page?: number;
    pageSize?: number;
    where?: Prisma.ShareCodeWhereInput;
  }): Promise<{
    items: Array<ShareCode & { resource?: Resource }>;
    totalPages: number;
  }> {
    try {
      console.log('args:', args.where.OR);
      // 使用include直接关联查询Resource
      const { items, totalPages } = await super.findManyWithPagination({
        ...args,
        select:{
          id: true,
          code: true,
          fileId: true,
          expiresAt: true,
          fileName: true,
          canUseTimes: true,
          resource: {
            select: {
              id: true,
              type: true,
              url: true,
              meta: true,
            }
          }
        }
      });
      this.logger.log('search result:', items);
      return {
        items,
        totalPages
      };
    } catch (error) {
      this.logger.error('Failed to get share codes with resources', error);
      throw error;
    }
  }
  async softDeleteShareCodes(ids: string[]): Promise<any> {
    try {
      this.logger.log(`尝试软删除分享码，IDs: ${ids.join(', ')}`);
      const result = await super.softDeleteByIds(ids);
      this.logger.log(`软删除分享码成功，数量: ${result.length}`);
      return result;
    } catch (error) {
      this.logger.error('软删除分享码失败', error);
      throw error;
    }
  }
  async updateShareCode(id: string, data: Partial<ShareCode>): Promise<any> {
    try {
      this.logger.log(`尝试更新分享码，ID: ${id}，数据:`, data);
      const result = await super.updateById(id, data);
      this.logger.log(`更新分享码成功:`, result);
      return result;
    } catch (error) {
      this.logger.error('更新分享码失败', error);
      throw error;
    }
  }
}
