import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { CreateSettlementDto } from './dto/create-settlement.dto';
import { UpdateSettlementDto } from './dto/update-settlement.dto';
import { AddPaymentDto } from './dto/add-payment.dto';
import { PaginationDto } from '../../common/dto/pagination.dto';
import { generateDocumentNumber } from '../../common/utils/document-number.util';

@Injectable()
export class SettlementsService {
  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 & { status?: string }) {
    const { page = 1, limit = 20, search, sortBy, sortOrder, status } = query as any;
    const skip = (page - 1) * limit;

    const where: any = { userId };

    if (status) {
      where.paymentStatus = status;
    }

    if (search) {
      where.OR = [
        { settlementNumber: { contains: search } },
        { note: { contains: search } },
        { noteEn: { contains: search } },
      ];
    }

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

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

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

  async findOne(userId: number, id: number) {
    const settlement = await this.prisma.settlement.findFirst({
      where: { id, userId },
      include: {
        client: true,
        project: true,
        contract: true,
        payments: true,
      },
    });

    if (!settlement) {
      throw new NotFoundException(`Settlement with ID ${id} not found`);
    }

    return this.serializeBigInt(settlement);
  }

  async create(userId: number, dto: CreateSettlementDto) {
    const settlementNumber = await generateDocumentNumber(
      this.prisma,
      'settlement',
      'STL',
      userId,
    );

    const amount = BigInt(dto.amount);
    const taxAmount = dto.taxAmount != null ? BigInt(dto.taxAmount) : BigInt(0);
    const totalAmount = amount + taxAmount;

    const settlement = await this.prisma.settlement.create({
      data: {
        userId,
        projectId: dto.projectId,
        clientId: dto.clientId,
        contractId: dto.contractId,
        settlementNumber,
        amount,
        taxAmount,
        totalAmount,
        settlementDate: new Date(dto.settlementDate),
        paymentMethod: dto.paymentMethod,
        paymentMethodEn: dto.paymentMethodEn,
        note: dto.note,
        noteEn: dto.noteEn,
      },
      include: {
        client: true,
        project: true,
        contract: true,
        payments: true,
      },
    });

    return this.serializeBigInt(settlement);
  }

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

    if (!existing) {
      throw new NotFoundException(`Settlement with ID ${id} not found`);
    }

    const data: any = { ...dto };

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

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

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

    const updated = await this.prisma.settlement.update({
      where: { id },
      data,
      include: {
        client: true,
        project: true,
        contract: true,
        payments: true,
      },
    });

    return this.serializeBigInt(updated);
  }

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

    if (!existing) {
      throw new NotFoundException(`Settlement with ID ${id} not found`);
    }

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

    return { message: `Settlement with ID ${id} has been deleted` };
  }

  async addPayment(userId: number, id: number, dto: AddPaymentDto) {
    const settlement = await this.prisma.settlement.findFirst({
      where: { id, userId },
      include: { payments: true },
    });

    if (!settlement) {
      throw new NotFoundException(`Settlement with ID ${id} not found`);
    }

    const payment = await this.prisma.settlementPayment.create({
      data: {
        settlementId: id,
        paymentDate: new Date(dto.paymentDate),
        amount: BigInt(dto.amount),
        method: dto.method,
        methodEn: dto.methodEn,
      },
    });

    // Recalculate payment status
    const allPayments = await this.prisma.settlementPayment.findMany({
      where: { settlementId: id },
    });

    const totalPaid = allPayments.reduce(
      (sum, p) => sum + p.amount,
      BigInt(0),
    );

    let paymentStatus: string;
    if (totalPaid >= settlement.totalAmount) {
      paymentStatus = 'paid';
    } else if (totalPaid > BigInt(0)) {
      paymentStatus = 'partial';
    } else {
      paymentStatus = 'waiting';
    }

    await this.prisma.settlement.update({
      where: { id },
      data: { paymentStatus },
    });

    return this.serializeBigInt(payment);
  }

  async removePayment(userId: number, settlementId: number, paymentId: number) {
    const settlement = await this.prisma.settlement.findFirst({
      where: { id: settlementId, userId },
    });

    if (!settlement) {
      throw new NotFoundException(
        `Settlement with ID ${settlementId} not found`,
      );
    }

    const payment = await this.prisma.settlementPayment.findFirst({
      where: { id: paymentId, settlementId },
    });

    if (!payment) {
      throw new NotFoundException(
        `Payment with ID ${paymentId} not found in settlement ${settlementId}`,
      );
    }

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

    // Recalculate payment status
    const remainingPayments = await this.prisma.settlementPayment.findMany({
      where: { settlementId },
    });

    const totalPaid = remainingPayments.reduce(
      (sum, p) => sum + p.amount,
      BigInt(0),
    );

    let paymentStatus: string;
    if (totalPaid >= settlement.totalAmount) {
      paymentStatus = 'paid';
    } else if (totalPaid > BigInt(0)) {
      paymentStatus = 'partial';
    } else {
      paymentStatus = 'waiting';
    }

    await this.prisma.settlement.update({
      where: { id: settlementId },
      data: { paymentStatus },
    });

    return { message: `Payment with ID ${paymentId} has been deleted` };
  }

  async getCalendar(userId: number, year: number, month: number) {
    const startDate = new Date(year, month - 1, 1);
    const endDate = new Date(year, month, 0, 23, 59, 59, 999);

    const settlements = await this.prisma.settlement.findMany({
      where: {
        userId,
        settlementDate: {
          gte: startDate,
          lte: endDate,
        },
      },
      include: {
        client: true,
        project: true,
        payments: true,
      },
      orderBy: { settlementDate: 'asc' },
    });

    // Group by date
    const grouped: Record<string, any[]> = {};
    for (const s of settlements) {
      const dateKey = s.settlementDate.toISOString().split('T')[0];
      if (!grouped[dateKey]) {
        grouped[dateKey] = [];
      }
      grouped[dateKey].push(s);
    }

    return this.serializeBigInt(grouped);
  }

  async getSummary(userId: number) {
    const [waiting, partial, paid] = await Promise.all([
      this.prisma.settlement.aggregate({
        where: { userId, paymentStatus: 'waiting' },
        _sum: { totalAmount: true },
        _count: true,
      }),
      this.prisma.settlement.aggregate({
        where: { userId, paymentStatus: 'partial' },
        _sum: { totalAmount: true },
        _count: true,
      }),
      this.prisma.settlement.aggregate({
        where: { userId, paymentStatus: 'paid' },
        _sum: { totalAmount: true },
        _count: true,
      }),
    ]);

    return this.serializeBigInt({
      waiting: {
        count: waiting._count,
        totalAmount: waiting._sum.totalAmount ?? 0,
      },
      partial: {
        count: partial._count,
        totalAmount: partial._sum.totalAmount ?? 0,
      },
      paid: {
        count: paid._count,
        totalAmount: paid._sum.totalAmount ?? 0,
      },
    });
  }
}
