import { ConflictException, Injectable, NotFoundException } from "@nestjs/common";
import { Prisma } from "@prisma/client";
import { newId } from "../common/ids/id.util";
import { slugify } from "../common/strings/slug.util";
import { PrismaService } from "../prisma/prisma.service";
import type { CreateMerchantDto } from "./dto/create-merchant.dto";
import type { UpdateMerchantDto } from "./dto/update-merchant.dto";

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

  async create(dto: CreateMerchantDto) {
    const slug = await this.uniqueSlug(dto.slug ?? slugify(dto.name));
    try {
      return await this.prisma.merchant.create({
        data: {
          id: newId("merchant"),
          name: dto.name,
          email: dto.email.toLowerCase(),
          slug,
          defaultCountry: dto.defaultCountry,
        },
      });
    } catch (err) {
      if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === "P2002") {
        throw new ConflictException("Email already in use");
      }
      throw err;
    }
  }

  async findOne(id: string) {
    const merchant = await this.prisma.merchant.findUnique({ where: { id } });
    if (!merchant) throw new NotFoundException("Merchant not found");
    return merchant;
  }

  async list(limit = 20, cursor?: string) {
    const take = Math.min(Math.max(limit, 1), 100);
    const rows = await this.prisma.merchant.findMany({
      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(id: string, dto: UpdateMerchantDto) {
    await this.findOne(id);
    try {
      return await this.prisma.merchant.update({
        where: { id },
        data: {
          ...(dto.name !== undefined && { name: dto.name }),
          ...(dto.email !== undefined && { email: dto.email.toLowerCase() }),
          ...(dto.defaultCountry !== undefined && { defaultCountry: dto.defaultCountry }),
          ...(dto.status !== undefined && { status: dto.status }),
        },
      });
    } catch (err) {
      if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === "P2002") {
        throw new ConflictException("Email already in use");
      }
      throw err;
    }
  }

  private async uniqueSlug(base: string): Promise<string> {
    const candidate = base || "merchant";
    for (let i = 0; i < 50; i++) {
      const slug = i === 0 ? candidate : `${candidate}-${i + 1}`;
      const exists = await this.prisma.merchant.findUnique({ where: { slug } });
      if (!exists) return slug;
    }
    throw new ConflictException("Could not generate a unique slug");
  }
}
