import {
  Injectable,
  NotFoundException,
  BadRequestException,
} from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { CreateCashReceiptDto } from './dto/create-cash-receipt.dto';
import { UpdateCashReceiptDto } from './dto/update-cash-receipt.dto';
import { PaginationDto } from '../../common/dto/pagination.dto';

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

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

  private async generateReceiptNumber(userId: number): Promise<string> {
    const year = new Date().getFullYear();
    const prefix = `CR-${year}-`;

    const lastReceipt = await this.prisma.cashReceipt.findFirst({
      where: {
        userId,
        receiptNumber: { startsWith: prefix },
      },
      orderBy: { receiptNumber: 'desc' },
      select: { receiptNumber: true },
    });

    let nextNumber = 1;
    if (lastReceipt) {
      const lastNumber = parseInt(
        lastReceipt.receiptNumber.replace(prefix, ''),
        10,
      );
      if (!isNaN(lastNumber)) {
        nextNumber = lastNumber + 1;
      }
    }

    return `${prefix}${String(nextNumber).padStart(3, '0')}`;
  }

  async findAll(userId: number, query: PaginationDto) {
    const { page = 1, limit = 20, search, sortBy, sortOrder } = query;
    const skip = (page - 1) * limit;

    const where: any = { userId };

    if (search) {
      where.OR = [
        { receiptNumber: { contains: search } },
        { consumerName: { contains: search } },
        { identityNumber: { contains: search } },
      ];
    }

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

    const [data, total] = await Promise.all([
      this.prisma.cashReceipt.findMany({
        where,
        skip,
        take: limit,
        orderBy,
        include: {
          items: { orderBy: { sortOrder: 'asc' } },
        },
      }),
      this.prisma.cashReceipt.count({ where }),
    ]);

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

  async findOne(userId: number, id: number) {
    const cashReceipt = await this.prisma.cashReceipt.findFirst({
      where: { id, userId },
      include: {
        items: { orderBy: { sortOrder: 'asc' } },
      },
    });

    if (!cashReceipt) {
      throw new NotFoundException(`Cash receipt with ID ${id} not found`);
    }

    return this.serializeBigInt(cashReceipt);
  }

  async create(userId: number, dto: CreateCashReceiptDto) {
    const receiptNumber = await this.generateReceiptNumber(userId);
    const taxAmount = dto.taxAmount !== undefined ? BigInt(dto.taxAmount) : BigInt(0);
    const totalAmount = BigInt(dto.supplyAmount) + taxAmount;

    const { items, ...receiptData } = dto;

    const data: any = {
      ...receiptData,
      userId,
      receiptNumber,
      issueDate: new Date(dto.issueDate),
      supplyAmount: BigInt(dto.supplyAmount),
      taxAmount,
      totalAmount,
      items: {
        create: items.map((item, index) => ({
          itemName: item.itemName,
          amount: BigInt(item.amount),
          sortOrder: item.sortOrder ?? index,
        })),
      },
    };

    const cashReceipt = await this.prisma.cashReceipt.create({
      data,
      include: {
        items: { orderBy: { sortOrder: 'asc' } },
      },
    });

    return this.serializeBigInt(cashReceipt);
  }

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

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

    if (existing.status === 'issued') {
      throw new BadRequestException('Cannot update an issued cash receipt');
    }

    const { items, ...receiptData } = dto;

    const data: any = {};

    if (receiptData.issueDate !== undefined)
      data.issueDate = new Date(receiptData.issueDate);
    if (receiptData.issueType !== undefined)
      data.issueType = receiptData.issueType;
    if (receiptData.identityType !== undefined)
      data.identityType = receiptData.identityType;
    if (receiptData.identityNumber !== undefined)
      data.identityNumber = receiptData.identityNumber;
    if (receiptData.consumerName !== undefined)
      data.consumerName = receiptData.consumerName;
    if (receiptData.supplyAmount !== undefined)
      data.supplyAmount = BigInt(receiptData.supplyAmount);
    if (receiptData.taxAmount !== undefined)
      data.taxAmount = BigInt(receiptData.taxAmount);
    if (
      receiptData.supplyAmount !== undefined ||
      receiptData.taxAmount !== undefined
    ) {
      const supply =
        receiptData.supplyAmount !== undefined
          ? BigInt(receiptData.supplyAmount)
          : existing.supplyAmount;
      const tax =
        receiptData.taxAmount !== undefined
          ? BigInt(receiptData.taxAmount)
          : existing.taxAmount;
      data.totalAmount = supply + tax;
    }
    if (receiptData.remark !== undefined) data.remark = receiptData.remark;

    // Replace items if provided
    if (items) {
      await this.prisma.cashReceiptItem.deleteMany({
        where: { cashReceiptId: id },
      });

      data.items = {
        create: items.map((item, index) => ({
          itemName: item.itemName,
          amount: BigInt(item.amount),
          sortOrder: item.sortOrder ?? index,
        })),
      };
    }

    const updated = await this.prisma.cashReceipt.update({
      where: { id },
      data,
      include: {
        items: { orderBy: { sortOrder: 'asc' } },
      },
    });

    return this.serializeBigInt(updated);
  }

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

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

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

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

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

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

    if (existing.status === 'issued') {
      throw new BadRequestException('Cash receipt is already issued');
    }

    const updated = await this.prisma.cashReceipt.update({
      where: { id },
      data: { status: 'issued' },
      include: {
        items: { orderBy: { sortOrder: 'asc' } },
      },
    });

    return this.serializeBigInt(updated);
  }
}
