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

@Injectable()
export class ContractsService {
  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 = [
        { contractNumber: { contains: search } },
      ];
    }

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

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

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

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

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

    return this.serializeBigInt(contract);
  }

  async create(userId: number, dto: CreateContractDto) {
    const contractNumber = await generateDocumentNumber(
      this.prisma,
      'contract',
      'CTR',
      userId,
    );

    const amount = BigInt(dto.amount);
    const taxAmount = dto.taxAmount !== undefined ? BigInt(dto.taxAmount) : amount / BigInt(10);
    const totalAmount = amount + taxAmount;

    const milestonesData = dto.milestones
      ? dto.milestones.map((m, index) => ({
          label: m.label,
          percentage: m.percentage ?? 0,
          amount: m.amount !== undefined ? BigInt(m.amount) : BigInt(0),
          conditionNote: m.conditionNote,
          sortOrder: m.sortOrder ?? index,
        }))
      : [];

    const contract = await this.prisma.contract.create({
      data: {
        userId,
        projectId: dto.projectId,
        clientId: dto.clientId,
        clientName: dto.clientName,
        projectName: dto.projectName,
        estimateId: dto.estimateId,
        contractNumber,
        startDate: dto.startDate ? new Date(dto.startDate) : undefined,
        endDate: dto.endDate ? new Date(dto.endDate) : undefined,
        amount,
        taxAmount,
        totalAmount,
        contractBody: dto.contractBody,
        templateId: dto.templateId,
        milestones: {
          create: milestonesData,
        },
      },
      include: {
        milestones: {
          orderBy: { sortOrder: 'asc' },
        },
        client: true,
        project: true,
        estimate: true,
      },
    });

    return this.serializeBigInt(contract);
  }

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

    if (!existing) {
      throw new NotFoundException(`Contract 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.estimateId !== undefined) data.estimateId = dto.estimateId;
    if (dto.startDate !== undefined) data.startDate = new Date(dto.startDate);
    if (dto.endDate !== undefined) data.endDate = new Date(dto.endDate);
    if (dto.contractBody !== undefined) data.contractBody = dto.contractBody;
    if (dto.templateId !== undefined) data.templateId = dto.templateId;

    // Recalculate amounts if amount or taxAmount changed
    if (dto.amount !== undefined || dto.taxAmount !== undefined) {
      const amount = dto.amount !== undefined ? BigInt(dto.amount) : existing.amount;
      const taxAmount = dto.taxAmount !== undefined ? BigInt(dto.taxAmount) : existing.taxAmount;
      const totalAmount = amount + taxAmount;

      data.amount = amount;
      data.taxAmount = taxAmount;
      data.totalAmount = totalAmount;
    }

    // If milestones are provided, replace them
    if (dto.milestones !== undefined) {
      const milestonesData = dto.milestones.map((m, index) => ({
        label: m.label,
        percentage: m.percentage ?? 0,
        amount: m.amount !== undefined ? BigInt(m.amount) : BigInt(0),
        conditionNote: m.conditionNote,
        sortOrder: m.sortOrder ?? index,
      }));

      const contract = await this.prisma.$transaction(async (tx) => {
        await tx.contractMilestone.deleteMany({ where: { contractId: id } });

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

      return this.serializeBigInt(contract);
    }

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

    return this.serializeBigInt(contract);
  }

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

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

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

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

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

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

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

    return this.serializeBigInt(contract);
  }

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

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

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

    return this.serializeBigInt(contract);
  }
}
