import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { IContractSigningTaskRepository } from '../../../domain/repositories/contract-signing-task.repository.interface';
import {
  ContractSigningTask,
  DeviceInfo,
} from '../../../domain/aggregates/contract-signing-task.aggregate';
import { ContractSigningStatus } from '../../../domain/value-objects/contract-signing-status.enum';

@Injectable()
export class ContractSigningTaskRepositoryImpl implements IContractSigningTaskRepository {
  constructor(private readonly prisma: PrismaService) {}

  async save(task: ContractSigningTask): Promise<ContractSigningTask> {
    const deviceInfoJson = task.signingDeviceInfo
      ? JSON.stringify(task.signingDeviceInfo)
      : null;

    if (task.id) {
      // 更新
      const updated = await this.prisma.contractSigningTask.update({
        where: { id: task.id },
        data: {
          status: task.status,
          scrolledToBottomAt: task.scrolledToBottomAt,
          acknowledgedAt: task.acknowledgedAt,
          signedAt: task.signedAt,
          signatureCloudUrl: task.signatureCloudUrl,
          signatureHash: task.signatureHash,
          signedPdfUrl: task.signedPdfUrl,
          signingIpAddress: task.signingIpAddress,
          signingDeviceInfo: deviceInfoJson,
          signingUserAgent: task.signingUserAgent,
          signingLatitude: task.signingLatitude
            ? new Prisma.Decimal(task.signingLatitude)
            : null,
          signingLongitude: task.signingLongitude
            ? new Prisma.Decimal(task.signingLongitude)
            : null,
        },
      });
      return this.mapToDomain(updated);
    } else {
      // 创建 - 使用重试机制处理并发幂等性
      // 先尝试查询，如果不存在再创建，如果创建失败（并发冲突）则重新查询
      const maxRetries = 3;
      for (let attempt = 1; attempt <= maxRetries; attempt++) {
        // 先检查是否已存在
        const existing = await this.prisma.contractSigningTask.findUnique({
          where: { orderNo: task.orderNo },
        });
        if (existing) {
          return this.mapToDomain(existing);
        }

        try {
          // 尝试创建
          const result = await this.prisma.contractSigningTask.create({
            data: {
              orderNo: task.orderNo,
              contractNo: task.contractNo,
              userId: task.userId,
              accountSequence: task.accountSequence,
              templateId: task.templateId,
              contractVersion: task.contractVersion,
              contractContent: task.contractContent,
              userPhoneNumber: task.userPhoneNumber,
              userRealName: task.userRealName,
              userIdCardNumber: task.userIdCardNumber,
              treeCount: task.treeCount,
              totalAmount: new Prisma.Decimal(task.totalAmount),
              provinceCode: task.provinceCode,
              provinceName: task.provinceName,
              cityCode: task.cityCode,
              cityName: task.cityName,
              status: task.status,
              expiresAt: task.expiresAt,
            },
          });
          return this.mapToDomain(result);
        } catch (error: unknown) {
          // 处理并发创建时的唯一约束冲突 (Prisma P2002)
          if (
            error &&
            typeof error === 'object' &&
            'code' in error &&
            error.code === 'P2002'
          ) {
            // 记录已被其他并发请求创建，等待一小段时间后重试查询
            if (attempt < maxRetries) {
              await new Promise((resolve) => setTimeout(resolve, 50 * attempt));
              continue;
            }
            // 最后一次尝试查询
            const finalExisting = await this.prisma.contractSigningTask.findUnique({
              where: { orderNo: task.orderNo },
            });
            if (finalExisting) {
              return this.mapToDomain(finalExisting);
            }
          }
          throw error;
        }
      }
      // 不应该到达这里，但为了类型安全
      throw new Error(`Failed to create or find signing task after ${maxRetries} attempts`);
    }
  }

  async findById(id: bigint): Promise<ContractSigningTask | null> {
    const task = await this.prisma.contractSigningTask.findUnique({
      where: { id },
    });
    return task ? this.mapToDomain(task) : null;
  }

  async findByOrderNo(orderNo: string): Promise<ContractSigningTask | null> {
    const task = await this.prisma.contractSigningTask.findUnique({
      where: { orderNo },
    });
    return task ? this.mapToDomain(task) : null;
  }

  async findPendingByUserId(userId: bigint): Promise<ContractSigningTask[]> {
    const tasks = await this.prisma.contractSigningTask.findMany({
      where: {
        userId,
        status: {
          in: [
            ContractSigningStatus.PENDING,
            ContractSigningStatus.SCROLLED,
            ContractSigningStatus.ACKNOWLEDGED,
          ],
        },
        expiresAt: { gte: new Date() },
      },
      orderBy: { createdAt: 'desc' },
    });
    return tasks.map((t) => this.mapToDomain(t));
  }

  async findUnsignedByUserId(userId: bigint): Promise<ContractSigningTask[]> {
    const tasks = await this.prisma.contractSigningTask.findMany({
      where: {
        userId,
        status: {
          notIn: [ContractSigningStatus.SIGNED],
        },
      },
      orderBy: { createdAt: 'desc' },
    });
    return tasks.map((t) => this.mapToDomain(t));
  }

  async findExpiredPendingTasks(): Promise<ContractSigningTask[]> {
    const now = new Date();
    const tasks = await this.prisma.contractSigningTask.findMany({
      where: {
        status: {
          in: [
            ContractSigningStatus.PENDING,
            ContractSigningStatus.SCROLLED,
            ContractSigningStatus.ACKNOWLEDGED,
          ],
        },
        expiresAt: { lt: now },
      },
      orderBy: { expiresAt: 'asc' },
    });
    return tasks.map((t) => this.mapToDomain(t));
  }

  async findByStatus(status: ContractSigningStatus): Promise<ContractSigningTask[]> {
    const tasks = await this.prisma.contractSigningTask.findMany({
      where: { status },
      orderBy: { createdAt: 'desc' },
    });
    return tasks.map((t) => this.mapToDomain(t));
  }

  async existsByOrderNo(orderNo: string): Promise<boolean> {
    const count = await this.prisma.contractSigningTask.count({
      where: { orderNo },
    });
    return count > 0;
  }

  private mapToDomain(data: {
    id: bigint;
    orderNo: string;
    contractNo: string;
    userId: bigint;
    accountSequence: string;
    templateId: number;
    contractVersion: string;
    contractContent: string;
    userPhoneNumber: string | null;
    userRealName: string | null;
    userIdCardNumber: string | null;
    treeCount: number;
    totalAmount: Prisma.Decimal;
    provinceCode: string;
    provinceName: string;
    cityCode: string;
    cityName: string;
    status: string;
    expiresAt: Date;
    scrolledToBottomAt: Date | null;
    acknowledgedAt: Date | null;
    signedAt: Date | null;
    signatureCloudUrl: string | null;
    signatureHash: string | null;
    signedPdfUrl: string | null;
    signingIpAddress: string | null;
    signingDeviceInfo: string | null;
    signingUserAgent: string | null;
    signingLatitude: Prisma.Decimal | null;
    signingLongitude: Prisma.Decimal | null;
    createdAt: Date;
    updatedAt: Date;
  }): ContractSigningTask {
    let deviceInfo: DeviceInfo | undefined;
    if (data.signingDeviceInfo) {
      try {
        deviceInfo = JSON.parse(data.signingDeviceInfo);
      } catch {
        deviceInfo = undefined;
      }
    }

    return ContractSigningTask.reconstitute({
      id: data.id,
      orderNo: data.orderNo,
      contractNo: data.contractNo,
      userId: data.userId,
      accountSequence: data.accountSequence,
      templateId: data.templateId,
      contractVersion: data.contractVersion,
      contractContent: data.contractContent,
      userPhoneNumber: data.userPhoneNumber ?? undefined,
      userRealName: data.userRealName ?? undefined,
      userIdCardNumber: data.userIdCardNumber ?? undefined,
      treeCount: data.treeCount,
      totalAmount: data.totalAmount.toNumber(),
      provinceCode: data.provinceCode,
      provinceName: data.provinceName,
      cityCode: data.cityCode,
      cityName: data.cityName,
      status: data.status,
      expiresAt: data.expiresAt,
      scrolledToBottomAt: data.scrolledToBottomAt ?? undefined,
      acknowledgedAt: data.acknowledgedAt ?? undefined,
      signedAt: data.signedAt ?? undefined,
      signatureCloudUrl: data.signatureCloudUrl ?? undefined,
      signatureHash: data.signatureHash ?? undefined,
      signedPdfUrl: data.signedPdfUrl ?? undefined,
      signingIpAddress: data.signingIpAddress ?? undefined,
      signingDeviceInfo: deviceInfo,
      signingUserAgent: data.signingUserAgent ?? undefined,
      signingLatitude: data.signingLatitude?.toNumber(),
      signingLongitude: data.signingLongitude?.toNumber(),
      createdAt: data.createdAt,
      updatedAt: data.updatedAt,
    });
  }
}
