/**
 * BaseService - Abstract Service with Common CRUD Operations
 *
 * Provides multi-tenant aware CRUD operations using TypeORM.
 * All domain services should extend this base class.
 *
 * @module @shared/services
 */

import {
  Repository,
  FindOptionsWhere,
  FindManyOptions,
  DeepPartial,
  ObjectLiteral,
} from 'typeorm';

export interface PaginationOptions {
  page?: number;
  limit?: number;
}

export interface PaginatedResult<T> {
  data: T[];
  meta: {
    total: number;
    page: number;
    limit: number;
    totalPages: number;
  };
}

export interface ServiceContext {
  tenantId: string;
  userId?: string;
}

export abstract class BaseService<T extends ObjectLiteral> {
  constructor(protected readonly repository: Repository<T>) {}

  /**
   * Find all records for a tenant with optional pagination
   */
  async findAll(
    ctx: ServiceContext,
    options?: PaginationOptions & { where?: FindOptionsWhere<T> }
  ): Promise<PaginatedResult<T>> {
    const page = options?.page || 1;
    const limit = options?.limit || 20;
    const skip = (page - 1) * limit;

    const where = {
      tenantId: ctx.tenantId,
      deletedAt: null,
      ...options?.where,
    } as unknown as FindOptionsWhere<T>;

    const [data, total] = await this.repository.findAndCount({
      where,
      take: limit,
      skip,
      order: { createdAt: 'DESC' } as any,
    });

    return {
      data,
      meta: {
        total,
        page,
        limit,
        totalPages: Math.ceil(total / limit),
      },
    };
  }

  /**
   * Find one record by ID for a tenant
   */
  async findById(ctx: ServiceContext, id: string): Promise<T | null> {
    return this.repository.findOne({
      where: {
        id,
        tenantId: ctx.tenantId,
        deletedAt: null,
      } as unknown as FindOptionsWhere<T>,
    });
  }

  /**
   * Find one record by criteria
   */
  async findOne(
    ctx: ServiceContext,
    where: FindOptionsWhere<T>
  ): Promise<T | null> {
    return this.repository.findOne({
      where: {
        tenantId: ctx.tenantId,
        deletedAt: null,
        ...where,
      } as FindOptionsWhere<T>,
    });
  }

  /**
   * Find records by custom options
   */
  async find(
    ctx: ServiceContext,
    options: FindManyOptions<T>
  ): Promise<T[]> {
    return this.repository.find({
      ...options,
      where: {
        tenantId: ctx.tenantId,
        deletedAt: null,
        ...(options.where || {}),
      } as FindOptionsWhere<T>,
    });
  }

  /**
   * Create a new record
   */
  async create(
    ctx: ServiceContext,
    data: DeepPartial<T>
  ): Promise<T> {
    const entity = this.repository.create({
      ...data,
      tenantId: ctx.tenantId,
      createdById: ctx.userId,
    } as DeepPartial<T>);

    return this.repository.save(entity);
  }

  /**
   * Update an existing record
   */
  async update(
    ctx: ServiceContext,
    id: string,
    data: DeepPartial<T>
  ): Promise<T | null> {
    const existing = await this.findById(ctx, id);
    if (!existing) {
      return null;
    }

    const updated = this.repository.merge(existing, {
      ...data,
      updatedById: ctx.userId,
    } as DeepPartial<T>);

    return this.repository.save(updated);
  }

  /**
   * Soft delete a record
   */
  async softDelete(ctx: ServiceContext, id: string): Promise<boolean> {
    const existing = await this.findById(ctx, id);
    if (!existing) {
      return false;
    }

    await this.repository.update(
      { id, tenantId: ctx.tenantId } as unknown as FindOptionsWhere<T>,
      {
        deletedAt: new Date(),
        deletedById: ctx.userId,
      } as any
    );

    return true;
  }

  /**
   * Hard delete a record (use with caution)
   */
  async hardDelete(ctx: ServiceContext, id: string): Promise<boolean> {
    const result = await this.repository.delete({
      id,
      tenantId: ctx.tenantId,
    } as unknown as FindOptionsWhere<T>);

    return (result.affected ?? 0) > 0;
  }

  /**
   * Count records
   */
  async count(
    ctx: ServiceContext,
    where?: FindOptionsWhere<T>
  ): Promise<number> {
    return this.repository.count({
      where: {
        tenantId: ctx.tenantId,
        deletedAt: null,
        ...where,
      } as unknown as FindOptionsWhere<T>,
    });
  }

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