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

@Injectable()
export class EstimatesService {
  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) {
    const { page = 1, limit = 20, search, sortBy, sortOrder } = query;
    const skip = (page - 1) * limit;

    const where: any = { userId };

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

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

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

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

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

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

    return this.serializeBigInt(estimate);
  }

  async create(userId: number, dto: CreateEstimateDto) {
    const estimateNumber = await generateDocumentNumber(
      this.prisma,
      'estimate',
      'EST',
      userId,
    );

    // Calculate amounts from items
    let subtotal = BigInt(0);
    const itemsData = dto.items.map((item, index) => {
      const quantity = item.quantity ?? 1;
      const unitPrice = BigInt(item.unitPrice);
      const amount = unitPrice * BigInt(quantity);
      subtotal += amount;

      return {
        description: item.description,
        category: item.category,
        quantity,
        unitPrice,
        amount,
        note: item.note,
        sortOrder: item.sortOrder ?? index,
      };
    });

    // Tax amount is 10% of subtotal (Korean VAT)
    const taxAmount = subtotal / BigInt(10);
    const totalAmount = subtotal + taxAmount;

    const estimate = await this.prisma.estimate.create({
      data: {
        userId,
        projectId: dto.projectId,
        clientId: dto.clientId,
        clientName: dto.clientName,
        projectName: dto.projectName,
        estimateNumber,
        issueDate: new Date(dto.issueDate),
        validDays: dto.validDays ?? 30,
        subtotal,
        taxAmount,
        totalAmount,
        memo: dto.memo,
        templateId: dto.templateId,
        items: {
          create: itemsData,
        },
      },
      include: {
        items: {
          orderBy: { sortOrder: 'asc' },
        },
        client: true,
        project: true,
      },
    });

    return this.serializeBigInt(estimate);
  }

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

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

    const data: any = {};

    if (dto.projectId !== undefined) data.projectId = dto.projectId;
    if (dto.clientId !== undefined) data.clientId = dto.clientId;
    if (dto.clientName !== undefined) data.clientName = dto.clientName;
    if (dto.projectName !== undefined) data.projectName = dto.projectName;
    if (dto.issueDate !== undefined) data.issueDate = new Date(dto.issueDate);
    if (dto.validDays !== undefined) data.validDays = dto.validDays;
    if (dto.memo !== undefined) data.memo = dto.memo;
    if (dto.templateId !== undefined) data.templateId = dto.templateId;

    // If items are provided, recalculate amounts and replace items
    if (dto.items !== undefined) {
      let subtotal = BigInt(0);
      const itemsData = dto.items.map((item, index) => {
        const quantity = item.quantity ?? 1;
        const unitPrice = BigInt(item.unitPrice);
        const amount = unitPrice * BigInt(quantity);
        subtotal += amount;

        return {
          description: item.description,
          category: item.category,
          quantity,
          unitPrice,
          amount,
          note: item.note,
          sortOrder: item.sortOrder ?? index,
        };
      });

      const taxAmount = subtotal / BigInt(10);
      const totalAmount = subtotal + taxAmount;

      data.subtotal = subtotal;
      data.taxAmount = taxAmount;
      data.totalAmount = totalAmount;

      // Delete old items and create new ones in a transaction
      const estimate = await this.prisma.$transaction(async (tx) => {
        await tx.estimateItem.deleteMany({ where: { estimateId: id } });

        return tx.estimate.update({
          where: { id },
          data: {
            ...data,
            items: {
              create: itemsData,
            },
          },
          include: {
            items: {
              orderBy: { sortOrder: 'asc' },
            },
            client: true,
            project: true,
          },
        });
      });

      return this.serializeBigInt(estimate);
    }

    const estimate = await this.prisma.estimate.update({
      where: { id },
      data,
      include: {
        items: {
          orderBy: { sortOrder: 'asc' },
        },
        client: true,
        project: true,
      },
    });

    return this.serializeBigInt(estimate);
  }

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

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

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

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

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

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

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

    return this.serializeBigInt(estimate);
  }

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

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

    const estimateNumber = await generateDocumentNumber(
      this.prisma,
      'estimate',
      'EST',
      userId,
    );

    const estimate = await this.prisma.estimate.create({
      data: {
        userId,
        projectId: existing.projectId,
        clientId: existing.clientId,
        clientName: existing.clientName,
        projectName: existing.projectName,
        estimateNumber,
        status: 'draft',
        issueDate: existing.issueDate,
        validDays: existing.validDays,
        subtotal: existing.subtotal,
        taxAmount: existing.taxAmount,
        totalAmount: existing.totalAmount,
        memo: existing.memo,
        templateId: existing.templateId,
        items: {
          create: existing.items.map((item) => ({
            description: item.description,
            category: item.category,
            quantity: item.quantity,
            unitPrice: item.unitPrice,
            amount: item.amount,
            note: item.note,
            sortOrder: item.sortOrder,
          })),
        },
      },
      include: {
        items: {
          orderBy: { sortOrder: 'asc' },
        },
        client: true,
        project: true,
      },
    });

    return this.serializeBigInt(estimate);
  }

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

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

    const contractNumber = await generateDocumentNumber(
      this.prisma,
      'contract',
      'CTR',
      userId,
    );

    const contract = await this.prisma.contract.create({
      data: {
        userId,
        projectId: existing.projectId,
        clientId: existing.clientId,
        clientName: existing.clientName,
        projectName: existing.projectName,
        estimateId: existing.id,
        contractNumber,
        status: 'draft',
        amount: existing.subtotal,
        taxAmount: existing.taxAmount,
        totalAmount: existing.totalAmount,
      },
      include: {
        milestones: {
          orderBy: { sortOrder: 'asc' },
        },
        client: true,
        project: true,
        estimate: true,
      },
    });

    return this.serializeBigInt(contract);
  }
}
