import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { CreateCategoryDto } from './dto/create-category.dto';
import { UpdateCategoryDto } from './dto/update-category.dto';
import { CreateServiceDto } from './dto/create-service.dto';
import { UpdateServiceDto } from './dto/update-service.dto';
import { CreatePackageDto } from './dto/create-package.dto';
import { UpdatePackageDto } from './dto/update-package.dto';
import { FindCategoriesDto, FindServicesDto, FindPackagesDto } from './dto/find-catalog.dto';
import { Prisma } from '@prisma/client';

export interface PaginatedResult<T> {
  data: T[];
  nextCursor: string | null;
  hasMore: boolean;
}

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

  // ─── Categories ──────────────────────────────────────────────────

  async findAllCategories(
    clubId: string,
    params: FindCategoriesDto,
  ): Promise<PaginatedResult<any>> {
    const { cursor, limit = 20 } = params;

    const items = await this.prisma.serviceCategory.findMany({
      where: { clubId },
      take: limit + 1,
      ...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
      orderBy: { sortOrder: 'asc' },
      include: { _count: { select: { services: true } } },
    });

    const hasMore = items.length > limit;
    const data = hasMore ? items.slice(0, -1) : items;

    return {
      data,
      nextCursor: hasMore ? data[data.length - 1].id : null,
      hasMore,
    };
  }

  async findCategoryById(clubId: string, id: string) {
    const category = await this.prisma.serviceCategory.findFirst({
      where: { id, clubId },
      include: {
        services: { select: { id: true, name: true, price: true, isActive: true } },
      },
    });
    if (!category) {
      throw new NotFoundException(`Category with id ${id} not found`);
    }
    return category;
  }

  async createCategory(clubId: string, dto: CreateCategoryDto) {
    return this.prisma.serviceCategory.create({
      data: { clubId, name: dto.name, sortOrder: dto.sortOrder ?? 0 },
    });
  }

  async updateCategory(clubId: string, id: string, dto: UpdateCategoryDto) {
    await this.ensureCategoryExists(clubId, id);
    return this.prisma.serviceCategory.update({
      where: { id },
      data: {
        ...(dto.name !== undefined ? { name: dto.name } : {}),
        ...(dto.sortOrder !== undefined ? { sortOrder: dto.sortOrder } : {}),
      },
    });
  }

  async removeCategory(clubId: string, id: string) {
    await this.ensureCategoryExists(clubId, id);
    await this.prisma.serviceCategory.delete({ where: { id } });
  }

  // ─── Services ────────────────────────────────────────────────────

  async findAllServices(
    clubId: string,
    params: FindServicesDto,
  ): Promise<PaginatedResult<any>> {
    const { cursor, limit = 20, categoryId, search, isActive } = params;

    const where: Prisma.ServiceWhereInput = {
      clubId,
      ...(categoryId ? { categoryId } : {}),
      ...(search
        ? { name: { contains: search, mode: 'insensitive' as const } }
        : {}),
      ...(isActive !== undefined ? { isActive } : {}),
    };

    const items = await this.prisma.service.findMany({
      where,
      take: limit + 1,
      ...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
      orderBy: { name: 'asc' },
      include: {
        category: { select: { id: true, name: true } },
        _count: { select: { packages: true } },
      },
    });

    const hasMore = items.length > limit;
    const data = hasMore ? items.slice(0, -1) : items;

    return {
      data,
      nextCursor: hasMore ? data[data.length - 1].id : null,
      hasMore,
    };
  }

  async findServiceById(clubId: string, id: string) {
    const service = await this.prisma.service.findFirst({
      where: { id, clubId },
      include: {
        category: { select: { id: true, name: true } },
        packages: {
          select: {
            id: true,
            name: true,
            price: true,
            totalSessions: true,
            validDays: true,
            isActive: true,
          },
        },
      },
    });
    if (!service) {
      throw new NotFoundException(`Service with id ${id} not found`);
    }
    return service;
  }

  async createService(clubId: string, dto: CreateServiceDto) {
    if (dto.categoryId) {
      await this.ensureCategoryExists(clubId, dto.categoryId);
    }

    return this.prisma.service.create({
      data: {
        clubId,
        name: dto.name,
        description: dto.description,
        price: dto.price,
        duration: dto.duration,
        categoryId: dto.categoryId,
        isActive: dto.isActive ?? true,
      },
      include: { category: { select: { id: true, name: true } } },
    });
  }

  async updateService(clubId: string, id: string, dto: UpdateServiceDto) {
    await this.ensureServiceExists(clubId, id);

    if (dto.categoryId) {
      await this.ensureCategoryExists(clubId, dto.categoryId);
    }

    return this.prisma.service.update({
      where: { id },
      data: {
        ...(dto.name !== undefined ? { name: dto.name } : {}),
        ...(dto.description !== undefined ? { description: dto.description } : {}),
        ...(dto.price !== undefined ? { price: dto.price } : {}),
        ...(dto.duration !== undefined ? { duration: dto.duration } : {}),
        ...(dto.categoryId !== undefined ? { categoryId: dto.categoryId } : {}),
        ...(dto.isActive !== undefined ? { isActive: dto.isActive } : {}),
      },
      include: { category: { select: { id: true, name: true } } },
    });
  }

  async removeService(clubId: string, id: string) {
    await this.ensureServiceExists(clubId, id);
    await this.prisma.service.delete({ where: { id } });
  }

  // ─── Packages ────────────────────────────────────────────────────

  async findAllPackages(
    clubId: string,
    params: FindPackagesDto,
  ): Promise<PaginatedResult<any>> {
    const { cursor, limit = 20, serviceId, isActive } = params;

    const where: Prisma.PackageWhereInput = {
      clubId,
      ...(serviceId ? { serviceId } : {}),
      ...(isActive !== undefined ? { isActive } : {}),
    };

    const items = await this.prisma.package.findMany({
      where,
      take: limit + 1,
      ...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
      orderBy: { name: 'asc' },
      include: { service: { select: { id: true, name: true } } },
    });

    const hasMore = items.length > limit;
    const data = hasMore ? items.slice(0, -1) : items;

    return {
      data,
      nextCursor: hasMore ? data[data.length - 1].id : null,
      hasMore,
    };
  }

  async findPackageById(clubId: string, id: string) {
    const pkg = await this.prisma.package.findFirst({
      where: { id, clubId },
      include: { service: { select: { id: true, name: true } } },
    });
    if (!pkg) {
      throw new NotFoundException(`Package with id ${id} not found`);
    }
    return pkg;
  }

  async createPackage(clubId: string, dto: CreatePackageDto) {
    await this.ensureServiceExists(clubId, dto.serviceId);

    return this.prisma.package.create({
      data: {
        clubId,
        name: dto.name,
        description: dto.description,
        price: dto.price,
        totalSessions: dto.totalSessions,
        validDays: dto.validDays,
        serviceId: dto.serviceId,
        isActive: dto.isActive ?? true,
      },
      include: { service: { select: { id: true, name: true } } },
    });
  }

  async updatePackage(clubId: string, id: string, dto: UpdatePackageDto) {
    await this.ensurePackageExists(clubId, id);

    if (dto.serviceId) {
      await this.ensureServiceExists(clubId, dto.serviceId);
    }

    return this.prisma.package.update({
      where: { id },
      data: {
        ...(dto.name !== undefined ? { name: dto.name } : {}),
        ...(dto.description !== undefined ? { description: dto.description } : {}),
        ...(dto.price !== undefined ? { price: dto.price } : {}),
        ...(dto.totalSessions !== undefined ? { totalSessions: dto.totalSessions } : {}),
        ...(dto.validDays !== undefined ? { validDays: dto.validDays } : {}),
        ...(dto.serviceId !== undefined ? { serviceId: dto.serviceId } : {}),
        ...(dto.isActive !== undefined ? { isActive: dto.isActive } : {}),
      },
      include: { service: { select: { id: true, name: true } } },
    });
  }

  async removePackage(clubId: string, id: string) {
    await this.ensurePackageExists(clubId, id);
    await this.prisma.package.delete({ where: { id } });
  }

  // ─── Helpers ─────────────────────────────────────────────────────

  private async ensureCategoryExists(clubId: string, id: string): Promise<void> {
    const count = await this.prisma.serviceCategory.count({ where: { id, clubId } });
    if (count === 0) {
      throw new NotFoundException(`Category with id ${id} not found`);
    }
  }

  private async ensureServiceExists(clubId: string, id: string): Promise<void> {
    const count = await this.prisma.service.count({ where: { id, clubId } });
    if (count === 0) {
      throw new NotFoundException(`Service with id ${id} not found`);
    }
  }

  private async ensurePackageExists(clubId: string, id: string): Promise<void> {
    const count = await this.prisma.package.count({ where: { id, clubId } });
    if (count === 0) {
      throw new NotFoundException(`Package with id ${id} not found`);
    }
  }
}
