import {
  Injectable,
  NotFoundException,
  BadRequestException,
} from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { CreateTaxInvoiceDto } from './dto/create-tax-invoice.dto';
import { UpdateTaxInvoiceDto } from './dto/update-tax-invoice.dto';
import { PaginationDto } from '../../common/dto/pagination.dto';

@Injectable()
export class TaxInvoicesService {
  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 generateInvoiceNumber(userId: number): Promise<string> {
    const year = new Date().getFullYear();
    const prefix = `TI-${year}-`;

    const lastInvoice = await this.prisma.taxInvoice.findFirst({
      where: {
        userId,
        invoiceNumber: { startsWith: prefix },
      },
      orderBy: { invoiceNumber: 'desc' },
      select: { invoiceNumber: true },
    });

    let nextNumber = 1;
    if (lastInvoice) {
      const lastNumber = parseInt(
        lastInvoice.invoiceNumber.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 = [
        { invoiceNumber: { contains: search } },
        { supplierName: { contains: search } },
        { recipientName: { contains: search } },
      ];
    }

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

    const [data, total] = await Promise.all([
      this.prisma.taxInvoice.findMany({
        where,
        skip,
        take: limit,
        orderBy,
        include: {
          items: { orderBy: { sortOrder: 'asc' } },
          client: {
            select: {
              id: true,
              name: true,
              nameEn: true,
            },
          },
        },
      }),
      this.prisma.taxInvoice.count({ where }),
    ]);

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

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

    if (!taxInvoice) {
      throw new NotFoundException(`Tax invoice with ID ${id} not found`);
    }

    return this.serializeBigInt(taxInvoice);
  }

  async create(userId: number, dto: CreateTaxInvoiceDto) {
    const invoiceNumber = await this.generateInvoiceNumber(userId);
    const totalAmount =
      BigInt(dto.supplyAmount) + BigInt(dto.taxAmount);

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

    const data: any = {
      ...invoiceData,
      userId,
      invoiceNumber,
      issueDate: new Date(dto.issueDate),
      supplyAmount: BigInt(dto.supplyAmount),
      taxAmount: BigInt(dto.taxAmount),
      totalAmount,
      paymentCash: dto.paymentCash !== undefined ? BigInt(dto.paymentCash) : BigInt(0),
      paymentCheck: dto.paymentCheck !== undefined ? BigInt(dto.paymentCheck) : BigInt(0),
      paymentNote: dto.paymentNote !== undefined ? BigInt(dto.paymentNote) : BigInt(0),
      paymentReceivable:
        dto.paymentReceivable !== undefined
          ? BigInt(dto.paymentReceivable)
          : BigInt(0),
      items: {
        create: items.map((item, index) => ({
          month: item.month,
          day: item.day,
          itemName: item.itemName,
          specification: item.specification,
          quantity: item.quantity ?? 1,
          unitPrice: BigInt(item.unitPrice),
          supplyAmount: BigInt(item.supplyAmount),
          taxAmount: item.taxAmount !== undefined ? BigInt(item.taxAmount) : BigInt(0),
          remark: item.remark,
          sortOrder: item.sortOrder ?? index,
        })),
      },
    };

    const taxInvoice = await this.prisma.taxInvoice.create({
      data,
      include: {
        items: { orderBy: { sortOrder: 'asc' } },
        client: {
          select: {
            id: true,
            name: true,
            nameEn: true,
          },
        },
      },
    });

    return this.serializeBigInt(taxInvoice);
  }

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

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

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

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

    const data: any = {};

    if (invoiceData.issueDate !== undefined)
      data.issueDate = new Date(invoiceData.issueDate);
    if (invoiceData.supplyAmount !== undefined)
      data.supplyAmount = BigInt(invoiceData.supplyAmount);
    if (invoiceData.taxAmount !== undefined)
      data.taxAmount = BigInt(invoiceData.taxAmount);
    if (
      invoiceData.supplyAmount !== undefined ||
      invoiceData.taxAmount !== undefined
    ) {
      const supply =
        invoiceData.supplyAmount !== undefined
          ? BigInt(invoiceData.supplyAmount)
          : existing.supplyAmount;
      const tax =
        invoiceData.taxAmount !== undefined
          ? BigInt(invoiceData.taxAmount)
          : existing.taxAmount;
      data.totalAmount = supply + tax;
    }
    if (invoiceData.projectId !== undefined)
      data.projectId = invoiceData.projectId;
    if (invoiceData.clientId !== undefined)
      data.clientId = invoiceData.clientId;
    if (invoiceData.clientName !== undefined)
      data.clientName = invoiceData.clientName;
    if (invoiceData.projectName !== undefined)
      data.projectName = invoiceData.projectName;
    if (invoiceData.supplierRegNo !== undefined)
      data.supplierRegNo = invoiceData.supplierRegNo;
    if (invoiceData.supplierName !== undefined)
      data.supplierName = invoiceData.supplierName;
    if (invoiceData.supplierRep !== undefined)
      data.supplierRep = invoiceData.supplierRep;
    if (invoiceData.supplierAddress !== undefined)
      data.supplierAddress = invoiceData.supplierAddress;
    if (invoiceData.supplierBizType !== undefined)
      data.supplierBizType = invoiceData.supplierBizType;
    if (invoiceData.supplierBizItem !== undefined)
      data.supplierBizItem = invoiceData.supplierBizItem;
    if (invoiceData.supplierContact !== undefined)
      data.supplierContact = invoiceData.supplierContact;
    if (invoiceData.supplierEmail !== undefined)
      data.supplierEmail = invoiceData.supplierEmail;
    if (invoiceData.recipientRegNo !== undefined)
      data.recipientRegNo = invoiceData.recipientRegNo;
    if (invoiceData.recipientName !== undefined)
      data.recipientName = invoiceData.recipientName;
    if (invoiceData.recipientRep !== undefined)
      data.recipientRep = invoiceData.recipientRep;
    if (invoiceData.recipientAddress !== undefined)
      data.recipientAddress = invoiceData.recipientAddress;
    if (invoiceData.recipientBizType !== undefined)
      data.recipientBizType = invoiceData.recipientBizType;
    if (invoiceData.recipientBizItem !== undefined)
      data.recipientBizItem = invoiceData.recipientBizItem;
    if (invoiceData.recipientContact !== undefined)
      data.recipientContact = invoiceData.recipientContact;
    if (invoiceData.recipientEmail !== undefined)
      data.recipientEmail = invoiceData.recipientEmail;
    if (invoiceData.paymentCash !== undefined)
      data.paymentCash = BigInt(invoiceData.paymentCash);
    if (invoiceData.paymentCheck !== undefined)
      data.paymentCheck = BigInt(invoiceData.paymentCheck);
    if (invoiceData.paymentNote !== undefined)
      data.paymentNote = BigInt(invoiceData.paymentNote);
    if (invoiceData.paymentReceivable !== undefined)
      data.paymentReceivable = BigInt(invoiceData.paymentReceivable);
    if (invoiceData.receiptType !== undefined)
      data.receiptType = invoiceData.receiptType;
    if (invoiceData.remark !== undefined) data.remark = invoiceData.remark;
    if (invoiceData.isCorrection !== undefined)
      data.isCorrection = invoiceData.isCorrection;
    if (invoiceData.originalInvoiceId !== undefined)
      data.originalInvoiceId = invoiceData.originalInvoiceId;

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

      data.items = {
        create: items.map((item, index) => ({
          month: item.month,
          day: item.day,
          itemName: item.itemName,
          specification: item.specification,
          quantity: item.quantity ?? 1,
          unitPrice: BigInt(item.unitPrice),
          supplyAmount: BigInt(item.supplyAmount),
          taxAmount: item.taxAmount !== undefined ? BigInt(item.taxAmount) : BigInt(0),
          remark: item.remark,
          sortOrder: item.sortOrder ?? index,
        })),
      };
    }

    const updated = await this.prisma.taxInvoice.update({
      where: { id },
      data,
      include: {
        items: { orderBy: { sortOrder: 'asc' } },
        client: {
          select: {
            id: true,
            name: true,
            nameEn: true,
          },
        },
      },
    });

    return this.serializeBigInt(updated);
  }

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

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

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

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

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

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

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

    const updated = await this.prisma.taxInvoice.update({
      where: { id },
      data: { status: 'issued' },
      include: {
        items: { orderBy: { sortOrder: 'asc' } },
        client: {
          select: {
            id: true,
            name: true,
            nameEn: true,
          },
        },
      },
    });

    return this.serializeBigInt(updated);
  }

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

    if (!original) {
      throw new NotFoundException(`Tax invoice with ID ${id} not found`);
    }

    if (original.status !== 'issued') {
      throw new BadRequestException(
        'Can only create correction for issued invoices',
      );
    }

    const invoiceNumber = await this.generateInvoiceNumber(userId);

    const correctionInvoice = await this.prisma.taxInvoice.create({
      data: {
        userId,
        invoiceNumber,
        status: 'draft',
        issueDate: new Date(),
        supplyAmount: original.supplyAmount,
        taxAmount: original.taxAmount,
        totalAmount: original.totalAmount,
        supplierRegNo: original.supplierRegNo,
        supplierName: original.supplierName,
        supplierRep: original.supplierRep,
        supplierAddress: original.supplierAddress,
        supplierBizType: original.supplierBizType,
        supplierBizItem: original.supplierBizItem,
        supplierContact: original.supplierContact,
        supplierEmail: original.supplierEmail,
        recipientRegNo: original.recipientRegNo,
        recipientName: original.recipientName,
        recipientRep: original.recipientRep,
        recipientAddress: original.recipientAddress,
        recipientBizType: original.recipientBizType,
        recipientBizItem: original.recipientBizItem,
        recipientContact: original.recipientContact,
        recipientEmail: original.recipientEmail,
        paymentCash: original.paymentCash,
        paymentCheck: original.paymentCheck,
        paymentNote: original.paymentNote,
        paymentReceivable: original.paymentReceivable,
        receiptType: original.receiptType,
        remark: original.remark,
        isCorrection: true,
        originalInvoiceId: original.id,
        projectId: original.projectId,
        clientId: original.clientId,
        clientName: original.clientName,
        projectName: original.projectName,
        items: {
          create: original.items.map((item, index) => ({
            month: item.month,
            day: item.day,
            itemName: item.itemName,
            specification: item.specification,
            quantity: item.quantity,
            unitPrice: item.unitPrice,
            supplyAmount: item.supplyAmount,
            taxAmount: item.taxAmount,
            remark: item.remark,
            sortOrder: item.sortOrder ?? index,
          })),
        },
      },
      include: {
        items: { orderBy: { sortOrder: 'asc' } },
        client: {
          select: {
            id: true,
            name: true,
            nameEn: true,
          },
        },
      },
    });

    return this.serializeBigInt(correctionInvoice);
  }
}
