import { Injectable, Logger } from '@nestjs/common';
import { PrismaService } from '@core/database/prisma/prisma.service';
import { EmailAdapter } from './adapters/email.adapter';
import { TemplateService } from './services/template.service';
import { NotificationChannel, NotificationSendStatus } from '@prisma/client';
import { SkipAssertAccess } from '@common/decorators/skip-assert-access.decorator';

export interface SendNotificationDto {
  channel: NotificationChannel;
  to: string; // userId or email
  subject?: string;
  content?: string;
  templateCode?: string;
  variables?: Record<string, any>;
  priority?: 'HIGH' | 'NORMAL' | 'LOW';
  metadata?: any;
}

@Injectable()
export class NotificationService {
  private readonly logger = new Logger(NotificationService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly emailAdapter: EmailAdapter,
    private readonly templateService: TemplateService,
  ) {}

  /**
   * 发送通知
   */
  async send(dto: SendNotificationDto) {
    this.logger.log(`Sending notification to ${dto.to} via ${dto.channel}`);

    try {
      // 1. 获取收件人信息
      const recipient = await this.getRecipient(dto.to);

      // 2. 渲染内容
      let subject = dto.subject;
      let content = dto.content;
      let templateId: string | undefined;

      if (dto.templateCode) {
        const rendered = await this.templateService.render(
          dto.templateCode,
          dto.variables || {},
        );
        subject = rendered.subject;
        content = rendered.content;
        
        const template = await this.templateService.findByCode(dto.templateCode);
        templateId = template.id;
      }

      if (!subject && !content) {
        throw new Error('Either subject/content or templateCode must be provided');
      }

      // 3. 创建日志记录
      const log = await this.prisma.notificationLog.create({
        data: {
          recipientId: recipient.id,
          recipientEmail: recipient.email,
          channel: dto.channel,
          templateId,
          subject: subject || '',
          content: content || '',
          variables: dto.variables || null,
          priority: dto.priority || 'NORMAL',
          metadata: dto.metadata || null,
          status: NotificationSendStatus.PENDING,
        } as any,
      });

      // 4. 异步发送（不阻塞）
      this.sendAsync(log.id).catch((error) => {
        this.logger.error(`Failed to send notification ${log.id}`, error);
      });

      return log;
    } catch (error) {
      this.logger.error('Failed to create notification', error);
      throw error;
    }
  }

  /**
   * 异步发送通知
   */
  @SkipAssertAccess('系统内部异步投递处理：写入对象是上一步刚由调用方权限校验后创建的 notificationLog（自身），无跨用户写入风险')
  private async sendAsync(logId: string) {
    const log = await this.prisma.notificationLog.findUnique({
      where: { id: logId },
    });

    if (!log) {
      this.logger.error(`Notification log ${logId} not found`);
      return;
    }

    try {
      // 更新为发送中
      await this.prisma.notificationLog.update({
        where: { id: logId },
        data: { status: NotificationSendStatus.SENDING },
      });

      // 根据渠道发送
      if (log.channel === NotificationChannel.EMAIL) {
        await this.emailAdapter.send({
          to: log.recipientEmail!,
          subject: log.subject!,
          html: log.content,
        });
      } else if (log.channel === NotificationChannel.IN_APP) {
        // 站内通知：notificationLog 记录本身即为站内通知载体，
        // 前端通过查询当前用户的 notificationLog（IN_APP 渠道、未读）展示。
        // 此处无需外部投递动作，落库即视为发送完成。
        this.logger.log(`In-app notification ${logId} persisted (no external delivery)`);
      } else {
        throw new Error(`Channel ${log.channel} not supported yet`);
      }

      // 更新为已发送
      await this.prisma.notificationLog.update({
        where: { id: logId },
        data: {
          status: NotificationSendStatus.SENT,
          sentAt: new Date(),
        },
      });

      this.logger.log(`Notification ${logId} sent successfully`);
    } catch (error: any) {
      this.logger.error(`Failed to send notification ${logId}`, error);

      // 更新为失败
      const retryCount = log.retryCount + 1;
      await this.prisma.notificationLog.update({
        where: { id: logId },
        data: {
          status: NotificationSendStatus.FAILED,
          errorMessage: error.message || error.toString(),
          retryCount,
          nextRetryAt: retryCount < log.maxRetries 
            ? this.calculateNextRetry(retryCount)
            : null,
        },
      });

      // 如果还有重试次数，安排重试
      if (retryCount < log.maxRetries) {
        this.logger.log(`Will retry notification ${logId} (${retryCount}/${log.maxRetries})`);
      }
    }
  }

  /**
   * 计算下次重试时间
   */
  private calculateNextRetry(retryCount: number): Date {
    const delays = [1, 5, 15]; // 分钟
    const delayMinutes = delays[Math.min(retryCount - 1, delays.length - 1)];
    return new Date(Date.now() + delayMinutes * 60 * 1000);
  }

  /**
   * 获取收件人信息
   */
  private async getRecipient(identifier: string): Promise<{ id: string; email: string }> {
    // 如果是邮箱格式
    if (identifier.includes('@')) {
      const user = await this.prisma.user.findUnique({
        where: { email: identifier },
        select: { id: true, email: true },
      });

      if (user) return user;

      // 如果找不到用户，允许直接发送到邮箱（创建临时记录）
      // 这里我们假设有一个 system 用户
      const systemUser = await this.prisma.user.findFirst({
        where: { username: 'system' },
        select: { id: true, email: true },
      });

      return systemUser || { id: 'system', email: identifier };
    }

    // 作为 userId 查找
    const user = await this.prisma.user.findUnique({
      where: { id: identifier },
      select: { id: true, email: true },
    });

    if (!user) {
      throw new Error(`User ${identifier} not found`);
    }

    if (!user.email) {
      throw new Error(`User ${identifier} has no email address`);
    }

    return user;
  }

  /**
   * 批量发送
   */
  async sendBatch(notifications: SendNotificationDto[]) {
    this.logger.log(`Sending batch of ${notifications.length} notifications`);
    
    const results = await Promise.allSettled(
      notifications.map(n => this.send(n))
    );

    const successful = results.filter(r => r.status === 'fulfilled').length;
    const failed = results.filter(r => r.status === 'rejected').length;

    this.logger.log(`Batch send complete: ${successful} successful, ${failed} failed`);

    return {
      total: notifications.length,
      successful,
      failed,
      results,
    };
  }

  /**
   * 获取通知日志
   */
  async getLogs(filters: {
    recipientId?: string;
    channel?: NotificationChannel;
    status?: NotificationSendStatus;
    startDate?: Date;
    endDate?: Date;
    page?: number;
    limit?: number;
  }) {
    const { page = 1, limit = 50, ...where } = filters;

    const whereClause: any = {};

    if (where.recipientId) whereClause.recipientId = where.recipientId;
    if (where.channel) whereClause.channel = where.channel;
    if (where.status) whereClause.status = where.status;

    if (where.startDate || where.endDate) {
      whereClause.createdAt = {};
      if (where.startDate) whereClause.createdAt.gte = where.startDate;
      if (where.endDate) whereClause.createdAt.lte = where.endDate;
    }

    const [logs, total] = await Promise.all([
      this.prisma.notificationLog.findMany({
        where: whereClause,
        include: {
          recipient: {
            select: {
              id: true,
              username: true,
              displayName: true,
              email: true,
            },
          },
          template: {
            select: {
              code: true,
              name: true,
            },
          },
        },
        orderBy: { createdAt: 'desc' },
        skip: (page - 1) * limit,
        take: limit,
      }),
      this.prisma.notificationLog.count({ where: whereClause }),
    ]);

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

  /**
   * 重试失败的通知
   */
  async retryFailed(logId: string) {
    const log = await this.prisma.notificationLog.findUnique({
      where: { id: logId },
    });

    if (!log) {
      throw new Error(`Notification log ${logId} not found`);
    }

    if (log.status !== NotificationSendStatus.FAILED) {
      throw new Error(`Notification ${logId} is not in FAILED status`);
    }

    if (log.retryCount >= log.maxRetries) {
      throw new Error(`Notification ${logId} has exceeded max retries`);
    }

    // 重置状态并重新发送
    await this.prisma.notificationLog.update({
      where: { id: logId },
      data: { status: NotificationSendStatus.PENDING },
    });

    await this.sendAsync(logId);

    return { success: true, message: 'Retry initiated' };
  }

  /**
   * 获取统计信息
   */
  async getStatistics(startDate?: Date, endDate?: Date) {
    const whereClause: any = {};

    if (startDate || endDate) {
      whereClause.createdAt = {};
      if (startDate) whereClause.createdAt.gte = startDate;
      if (endDate) whereClause.createdAt.lte = endDate;
    }

    const [total, sent, failed, pending] = await Promise.all([
      this.prisma.notificationLog.count({ where: whereClause }),
      this.prisma.notificationLog.count({
        where: { ...whereClause, status: NotificationSendStatus.SENT },
      }),
      this.prisma.notificationLog.count({
        where: { ...whereClause, status: NotificationSendStatus.FAILED },
      }),
      this.prisma.notificationLog.count({
        where: { ...whereClause, status: NotificationSendStatus.PENDING },
      }),
    ]);

    const byChannel = await this.prisma.notificationLog.groupBy({
      by: ['channel'],
      _count: true,
      where: whereClause,
    });

    return {
      total,
      sent,
      failed,
      pending,
      successRate: total > 0 ? ((sent / total) * 100).toFixed(2) + '%' : '0%',
      byChannel: byChannel.map((c: any) => ({
        channel: c.channel,
        count: c._count,
      })),
    };
  }
}
