import { ConflictException, Injectable, NotFoundException } from "@nestjs/common";
import { Prisma } from "@prisma/client";
import { newId } from "../common/ids/id.util";
import { PrismaService } from "../prisma/prisma.service";
import type { CreateCustomerDto } from "./dto/create-customer.dto";
import type { UpdateCustomerDto } from "./dto/update-customer.dto";

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

  async create(merchantId: string, dto: CreateCustomerDto) {
    try {
      return await this.prisma.customer.create({
        data: {
          id: newId("customer"),
          merchantId,
          email: dto.email?.toLowerCase() ?? null,
          name: dto.name ?? null,
          docType: dto.docType ?? null,
          docNumber: dto.docNumber ?? null,
          phone: dto.phone ?? null,
          country: dto.country ?? null,
          metadata: (dto.metadata as Prisma.InputJsonValue) ?? Prisma.JsonNull,
        },
      });
    } catch (err) {
      if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === "P2002") {
        throw new ConflictException("Customer with same email or document already exists");
      }
      throw err;
    }
  }

  async findOne(merchantId: string, id: string) {
    const customer = await this.prisma.customer.findFirst({ where: { id, merchantId } });
    if (!customer) throw new NotFoundException("Customer not found");
    return customer;
  }

  async list(
    merchantId: string,
    limit = 20,
    cursor?: string,
    filters?: { email?: string; docNumber?: string },
  ) {
    const take = Math.min(Math.max(limit, 1), 100);
    const rows = await this.prisma.customer.findMany({
      where: {
        merchantId,
        ...(filters?.email ? { email: filters.email.toLowerCase() } : {}),
        ...(filters?.docNumber ? { docNumber: filters.docNumber } : {}),
      },
      take: take + 1,
      ...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
      orderBy: { createdAt: "desc" },
    });
    const hasMore = rows.length > take;
    const data = hasMore ? rows.slice(0, take) : rows;
    return { data, nextCursor: hasMore ? data[data.length - 1].id : null };
  }

  async update(merchantId: string, id: string, dto: UpdateCustomerDto) {
    await this.findOne(merchantId, id);
    try {
      return await this.prisma.customer.update({
        where: { id },
        data: {
          ...(dto.email !== undefined && { email: dto.email?.toLowerCase() ?? null }),
          ...(dto.name !== undefined && { name: dto.name ?? null }),
          ...(dto.docType !== undefined && { docType: dto.docType ?? null }),
          ...(dto.docNumber !== undefined && { docNumber: dto.docNumber ?? null }),
          ...(dto.phone !== undefined && { phone: dto.phone ?? null }),
          ...(dto.country !== undefined && { country: dto.country ?? null }),
          ...(dto.metadata !== undefined && { metadata: dto.metadata as Prisma.InputJsonValue }),
        },
      });
    } catch (err) {
      if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === "P2002") {
        throw new ConflictException("Customer with same email or document already exists");
      }
      throw err;
    }
  }
}
