import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '@/infrastructure/persistence/prisma/prisma.service';
import {
  ILedgerEntryRepository, LedgerFilters, Pagination, PaginatedResult,
} from '@/domain/repositories';
import { LedgerEntry } from '@/domain/aggregates';
import { RepositorySaveOptions, PrismaTransactionClient } from '@/infrastructure/persistence/unit-of-work';
import Decimal from 'decimal.js';

@Injectable()
export class LedgerEntryRepositoryImpl implements ILedgerEntryRepository {
  constructor(private readonly prisma: PrismaService) {}

  private getClient(options?: RepositorySaveOptions): PrismaService | PrismaTransactionClient {
    return options?.tx || this.prisma;
  }

  async save(entry: LedgerEntry, options?: RepositorySaveOptions): Promise<LedgerEntry> {
    const client = this.getClient(options);
    const created = await client.ledgerEntry.create({
      data: {
        accountSequence: entry.accountSequence,
        userId: entry.userId.value,
        entryType: entry.entryType,
        amount: entry.amount.toDecimal(),
        assetType: entry.assetType,
        balanceAfter: entry.balanceAfter?.toDecimal() ?? null,
        refOrderId: entry.refOrderId,
        refTxHash: entry.refTxHash,
        memo: entry.memo,
        payloadJson: entry.payloadJson as Prisma.InputJsonValue ?? Prisma.JsonNull,
      },
    });
    return this.toDomain(created);
  }

  async saveAll(entries: LedgerEntry[], options?: RepositorySaveOptions): Promise<void> {
    const client = this.getClient(options);
    await client.ledgerEntry.createMany({
      data: entries.map(entry => ({
        accountSequence: entry.accountSequence,
        userId: entry.userId.value,
        entryType: entry.entryType,
        amount: entry.amount.toDecimal(),
        assetType: entry.assetType,
        balanceAfter: entry.balanceAfter?.toDecimal() ?? null,
        refOrderId: entry.refOrderId,
        refTxHash: entry.refTxHash,
        memo: entry.memo,
        payloadJson: entry.payloadJson as Prisma.InputJsonValue ?? Prisma.JsonNull,
      })),
    });
  }

  // 临时性流水类型，不在流水明细中显示
  private static readonly HIDDEN_ENTRY_TYPES = [
    'PLANT_FREEZE',      // 认种冻结（临时）
    'PLANT_UNFREEZE',    // 认种解冻（回滚）
    'FREEZE',            // 通用冻结（临时）
    'UNFREEZE',          // 通用解冻（回滚）
  ];

  async findByUserId(
    userId: bigint,
    filters?: LedgerFilters,
    pagination?: Pagination,
  ): Promise<PaginatedResult<LedgerEntry>> {
    const where: Record<string, unknown> = {
      userId,
      // 排除临时性流水类型
      entryType: { notIn: LedgerEntryRepositoryImpl.HIDDEN_ENTRY_TYPES },
    };

    if (filters?.entryType) {
      // 如果用户指定了类型筛选，则使用用户指定的类型
      where.entryType = filters.entryType;
    }
    if (filters?.assetType) {
      where.assetType = filters.assetType;
    }
    if (filters?.startDate || filters?.endDate) {
      where.createdAt = {};
      if (filters.startDate) {
        (where.createdAt as Record<string, unknown>).gte = filters.startDate;
      }
      if (filters.endDate) {
        (where.createdAt as Record<string, unknown>).lte = filters.endDate;
      }
    }

    const page = pagination?.page ?? 1;
    const pageSize = pagination?.pageSize ?? 20;
    const skip = (page - 1) * pageSize;

    const [records, total] = await Promise.all([
      this.prisma.ledgerEntry.findMany({
        where,
        orderBy: { createdAt: 'desc' },
        skip,
        take: pageSize,
      }),
      this.prisma.ledgerEntry.count({ where }),
    ]);

    return {
      data: records.map(r => this.toDomain(r)),
      total,
      page,
      pageSize,
      totalPages: Math.ceil(total / pageSize),
    };
  }

  async findByRefOrderId(refOrderId: string): Promise<LedgerEntry[]> {
    const records = await this.prisma.ledgerEntry.findMany({
      where: { refOrderId },
      orderBy: { createdAt: 'desc' },
    });
    return records.map(r => this.toDomain(r));
  }

  async findByRefTxHash(refTxHash: string): Promise<LedgerEntry[]> {
    const records = await this.prisma.ledgerEntry.findMany({
      where: { refTxHash },
      orderBy: { createdAt: 'desc' },
    });
    return records.map(r => this.toDomain(r));
  }

  async findByAccountSequence(
    accountSequence: string,
    filters?: LedgerFilters,
    pagination?: Pagination,
  ): Promise<PaginatedResult<LedgerEntry>> {
    const where: Record<string, unknown> = {
      accountSequence,
      // 排除临时性流水类型
      entryType: { notIn: LedgerEntryRepositoryImpl.HIDDEN_ENTRY_TYPES },
    };

    if (filters?.entryType) {
      // 如果用户指定了类型筛选，则使用用户指定的类型
      where.entryType = filters.entryType;
    }
    if (filters?.assetType) {
      where.assetType = filters.assetType;
    }
    if (filters?.startDate || filters?.endDate) {
      where.createdAt = {};
      if (filters.startDate) {
        (where.createdAt as Record<string, unknown>).gte = filters.startDate;
      }
      if (filters.endDate) {
        (where.createdAt as Record<string, unknown>).lte = filters.endDate;
      }
    }

    const page = pagination?.page ?? 1;
    const pageSize = pagination?.pageSize ?? 20;
    const skip = (page - 1) * pageSize;

    const [records, total] = await Promise.all([
      this.prisma.ledgerEntry.findMany({
        where,
        orderBy: { createdAt: 'desc' },
        skip,
        take: pageSize,
      }),
      this.prisma.ledgerEntry.count({ where }),
    ]);

    return {
      data: records.map(r => this.toDomain(r)),
      total,
      page,
      pageSize,
      totalPages: Math.ceil(total / pageSize),
    };
  }

  private toDomain(record: {
    id: bigint;
    accountSequence: string;
    userId: bigint;
    entryType: string;
    amount: Decimal;
    assetType: string;
    balanceAfter: Decimal | null;
    refOrderId: string | null;
    refTxHash: string | null;
    memo: string | null;
    payloadJson: unknown;
    createdAt: Date;
  }): LedgerEntry {
    return LedgerEntry.reconstruct({
      id: record.id,
      accountSequence: record.accountSequence,
      userId: record.userId,
      entryType: record.entryType,
      amount: new Decimal(record.amount.toString()),
      assetType: record.assetType,
      balanceAfter: record.balanceAfter ? new Decimal(record.balanceAfter.toString()) : null,
      refOrderId: record.refOrderId,
      refTxHash: record.refTxHash,
      memo: record.memo,
      payloadJson: record.payloadJson as Record<string, unknown> | null,
      createdAt: record.createdAt,
    });
  }
}
