import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { CreatePurchaseSaleDto } from './dto/create-purchase-sale.dto';
import { UpdatePurchaseSaleDto } from './dto/update-purchase-sale.dto';
import { PaginationDto } from '../../common/dto/pagination.dto';
import { generateDocumentNumber } from '../../common/utils/document-number.util';

@Injectable()
export class PurchaseSalesService {
  constructor(private readonly prisma: PrismaService) {}

  private serializeBigInt(data: any): any {
    return JSON.parse(
      JSON.stringify(data, (_, v) => (typeof v === 'bigint' ? Number(v) : v)),
    );
  }

  async findAll(userId: number, query: PaginationDto & { tradeType?: string; projectId?: number }) {
    const { page = 1, limit = 20, search, sortBy, sortOrder, tradeType, projectId } = query as any;
    const skip = (page - 1) * limit;

    const where: any = { userId };

    if (tradeType) {
      where.tradeType = tradeType;
    }

    if (projectId) {
      where.projectId = Number(projectId);
    }

    if (search) {
      where.OR = [
        { recordNumber: { contains: search } },
        { partner: { contains: search } },
        { partnerEn: { contains: search } },
        { memo: { contains: search } },
        { memoEn: { contains: search } },
      ];
    }

    const orderBy: any = {};
    if (sortBy) {
      orderBy[sortBy] = sortOrder || 'asc';
    } else {
      orderBy.createdAt = 'desc';
    }

    const [data, total] = await Promise.all([
      this.prisma.purchaseSaleRecord.findMany({
        where,
        skip,
        take: limit,
        orderBy,
        include: { project: true },
      }),
      this.prisma.purchaseSaleRecord.count({ where }),
    ]);

    return {
      data: this.serializeBigInt(data),
      total,
      page,
      limit,
    };
  }

  async findOne(userId: number, id: number) {
    const record = await this.prisma.purchaseSaleRecord.findFirst({
      where: { id, userId },
      include: { project: true },
    });

    if (!record) {
      throw new NotFoundException(
        `Purchase/Sale record with ID ${id} not found`,
      );
    }

    return this.serializeBigInt(record);
  }

  async create(userId: number, dto: CreatePurchaseSaleDto) {
    const recordNumber = await generateDocumentNumber(
      this.prisma,
      'purchaseSaleRecord',
      'PS',
      userId,
    );

    const supplyAmount = BigInt(dto.supplyAmount);
    const taxAmount = dto.taxAmount != null ? BigInt(dto.taxAmount) : BigInt(0);
    const total = supplyAmount + taxAmount;

    const record = await this.prisma.purchaseSaleRecord.create({
      data: {
        userId,
        recordNumber,
        recordDate: new Date(dto.recordDate),
        tradeType: dto.tradeType,
        docType: dto.docType,
        partner: dto.partner,
        partnerEn: dto.partnerEn,
        partnerRegNo: dto.partnerRegNo,
        supplyAmount,
        taxAmount,
        total,
        memo: dto.memo,
        memoEn: dto.memoEn,
        projectId: dto.projectId,
        projectName: dto.projectName,
        milestoneType: dto.milestoneType,
        dueDate: dto.dueDate ? new Date(dto.dueDate) : undefined,
        source: dto.source,
        linkedSettlementId: dto.linkedSettlementId,
      },
    });

    return this.serializeBigInt(record);
  }

  async update(userId: number, id: number, dto: UpdatePurchaseSaleDto) {
    const existing = await this.prisma.purchaseSaleRecord.findFirst({
      where: { id, userId },
    });

    if (!existing) {
      throw new NotFoundException(
        `Purchase/Sale record with ID ${id} not found`,
      );
    }

    const data: any = {};

    // Map DTO fields to Prisma-compatible data
    if (dto.tradeType !== undefined) data.tradeType = dto.tradeType;
    if (dto.docType !== undefined) data.docType = dto.docType;
    if (dto.partner !== undefined) data.partner = dto.partner;
    if (dto.partnerEn !== undefined) data.partnerEn = dto.partnerEn;
    if (dto.partnerRegNo !== undefined) data.partnerRegNo = dto.partnerRegNo;
    if (dto.memo !== undefined) data.memo = dto.memo;
    if (dto.memoEn !== undefined) data.memoEn = dto.memoEn;
    if (dto.projectId !== undefined) data.projectId = dto.projectId;
    if (dto.projectName !== undefined) data.projectName = dto.projectName;
    if (dto.milestoneType !== undefined) data.milestoneType = dto.milestoneType;
    if (dto.source !== undefined) data.source = dto.source;
    if (dto.linkedSettlementId !== undefined) data.linkedSettlementId = dto.linkedSettlementId;

    if (dto.recordDate) {
      data.recordDate = new Date(dto.recordDate);
    }
    if (dto.dueDate) {
      data.dueDate = new Date(dto.dueDate);
    }

    if (dto.supplyAmount != null) {
      data.supplyAmount = BigInt(dto.supplyAmount);
    }
    if (dto.taxAmount != null) {
      data.taxAmount = BigInt(dto.taxAmount);
    }

    // Recalculate total if supplyAmount or taxAmount changed
    if (dto.supplyAmount != null || dto.taxAmount != null) {
      const newSupplyAmount =
        dto.supplyAmount != null ? BigInt(dto.supplyAmount) : existing.supplyAmount;
      const newTaxAmount =
        dto.taxAmount != null ? BigInt(dto.taxAmount) : existing.taxAmount;
      data.total = newSupplyAmount + newTaxAmount;
    }

    const updated = await this.prisma.purchaseSaleRecord.update({
      where: { id },
      data,
    });

    return this.serializeBigInt(updated);
  }

  async remove(userId: number, id: number) {
    const existing = await this.prisma.purchaseSaleRecord.findFirst({
      where: { id, userId },
    });

    if (!existing) {
      throw new NotFoundException(
        `Purchase/Sale record with ID ${id} not found`,
      );
    }

    await this.prisma.purchaseSaleRecord.delete({ where: { id } });

    return { message: `Purchase/Sale record with ID ${id} has been deleted` };
  }

  async getSummary(userId: number, year?: number, month?: number) {
    const where: any = { userId };

    if (year) {
      const startDate = month
        ? new Date(year, month - 1, 1)
        : new Date(year, 0, 1);
      const endDate = month
        ? new Date(year, month, 0, 23, 59, 59, 999)
        : new Date(year, 11, 31, 23, 59, 59, 999);

      where.recordDate = {
        gte: startDate,
        lte: endDate,
      };
    }

    const [purchaseResult, saleResult] = await Promise.all([
      this.prisma.purchaseSaleRecord.aggregate({
        where: { ...where, tradeType: 'purchase' },
        _sum: {
          supplyAmount: true,
          taxAmount: true,
          total: true,
        },
        _count: true,
      }),
      this.prisma.purchaseSaleRecord.aggregate({
        where: { ...where, tradeType: 'sale' },
        _sum: {
          supplyAmount: true,
          taxAmount: true,
          total: true,
        },
        _count: true,
      }),
    ]);

    return this.serializeBigInt({
      purchase: {
        count: purchaseResult._count,
        supplyAmount: purchaseResult._sum.supplyAmount ?? 0,
        taxAmount: purchaseResult._sum.taxAmount ?? 0,
        total: purchaseResult._sum.total ?? 0,
      },
      sale: {
        count: saleResult._count,
        supplyAmount: saleResult._sum.supplyAmount ?? 0,
        taxAmount: saleResult._sum.taxAmount ?? 0,
        total: saleResult._sum.total ?? 0,
      },
    });
  }
}
