import { NotFoundException, Logger } from "@nestjs/common";
import { PrismaService } from "../../database/prisma.service";
import { PaginationDto } from "../dto/pagination.dto";
import { PaginationMeta } from "../types/api-response.type";

/**
 * Generic base service with common CRUD operations
 * Extend this class for entity-specific services
 */
export abstract class BaseService<T, CreateDto, UpdateDto> {
  protected readonly logger: Logger;

  constructor(
    protected readonly prisma: PrismaService,
    protected readonly modelName: string,
  ) {
    this.logger = new Logger(`${modelName}Service`);
  }

  /**
   * Get the Prisma model delegate
   */
  protected get model() {
    return (this.prisma as any)[this.modelName.toLowerCase()];
  }

  /**
   * Find all records with pagination
   */
  async findAll(
    pagination: PaginationDto,
    where?: any,
  ): Promise<{ items: T[]; meta: PaginationMeta }> {
    const { skip, take, orderBy } = pagination;

    const [items, total] = await Promise.all([
      this.model.findMany({
        where,
        skip,
        take,
        orderBy,
      }),
      this.model.count({ where }),
    ]);

    const totalPages = Math.ceil(total / take);

    return {
      items,
      meta: {
        total,
        page: pagination.page || 1,
        limit: pagination.limit || 10,
        totalPages,
        hasNextPage: (pagination.page || 1) < totalPages,
        hasPreviousPage: (pagination.page || 1) > 1,
      },
    };
  }

  /**
   * Find a single record by ID
   */
  async findOne(id: string, include?: any): Promise<T> {
    const record = await this.model.findUnique({
      where: { id },
      include,
    });

    if (!record) {
      throw new NotFoundException(`${this.modelName} not found`);
    }

    return record;
  }

  /**
   * Find a single record by custom criteria
   */
  findOneBy(where: any, include?: any): Promise<T | null> {
    return this.model.findFirst({
      where,
      include,
    });
  }

  /**
   * Create a new record
   */
  create(data: CreateDto, include?: any): Promise<T> {
    return this.model.create({
      data,
      include,
    });
  }

  /**
   * Update an existing record
   */
  async update(id: string, data: UpdateDto, include?: any): Promise<T> {
    // Check if record exists
    await this.findOne(id);

    return this.model.update({
      where: { id },
      data,
      include,
    });
  }

  /**
   * Soft delete a record (sets deletedAt)
   */
  async delete(id: string): Promise<T> {
    // Check if record exists
    await this.findOne(id);

    return this.model.delete({
      where: { id },
    });
  }

  /**
   * Hard delete a record (permanently removes)
   */
  async hardDelete(id: string): Promise<T> {
    // Check if record exists
    await this.findOne(id);

    return this.prisma.hardDelete(this.modelName, { id });
  }

  /**
   * Restore a soft-deleted record
   */
  async restore(id: string): Promise<T> {
    return this.prisma.restore(this.modelName, { id });
  }

  /**
   * Check if a record exists
   */
  async exists(id: string): Promise<boolean> {
    const count = await this.model.count({
      where: { id },
    });
    return count > 0;
  }

  /**
   * Count records matching criteria
   */
  count(where?: any): Promise<number> {
    return this.model.count({ where });
  }

  /**
   * Execute a transaction
   */
  transaction<R>(fn: (prisma: PrismaService) => Promise<R>): Promise<R> {
    return this.prisma.$transaction(async (tx) => {
      return fn(tx as unknown as PrismaService);
    });
  }
}
