import { logger } from '@goodgo/logger';
import { PrismaClient } from '@prisma/client';

import { DatabaseError } from '../../errors/http-error';
import { formatErrorForLogging, hasCode } from '../../utils/error-utils';

/**
 * EN: Base repository class providing common database operations
 * VI: Base repository class cung cấp các thao tác database chung
 */
export abstract class BaseRepository<T, CreateInput, UpdateInput> {
  protected prisma: PrismaClient;
  protected modelName: string;

  constructor(prisma: PrismaClient, modelName: string) {
    this.prisma = prisma;
    this.modelName = modelName;
  }

  /**
   * EN: Find entity by ID
   * VI: Tìm entity theo ID
   */
  async findById(id: string): Promise<T | null> {
    try {
      logger.debug(`Finding ${this.modelName} by ID / Tìm ${this.modelName} theo ID`, { id });

      const entity = await (this.prisma as any)[this.modelName].findUnique({
        where: { id },
      });

      logger.debug(`${this.modelName} ${entity ? 'found' : 'not found'} / ${this.modelName} ${entity ? 'đã tìm thấy' : 'không tìm thấy'}`, { id });
      return entity;
    } catch (error: unknown) {
      logger.error(`Failed to find ${this.modelName} by ID / Không thể tìm ${this.modelName} theo ID`, { ...formatErrorForLogging(error), id });
      throw new DatabaseError(`Failed to find ${this.modelName}`, { id, originalError: error });
    }
  }

  /**
   * EN: Find entity by unique field
   * VI: Tìm entity theo field duy nhất
   */
  async findByUnique(field: string, value: unknown): Promise<T | null> {
    try {
      logger.debug(`Finding ${this.modelName} by ${field} / Tìm ${this.modelName} theo ${field}`, { field, value });

      const entity = await (this.prisma as any)[this.modelName].findUnique({
        where: { [field]: value },
      });

      logger.debug(`${this.modelName} ${entity ? 'found' : 'not found'} / ${this.modelName} ${entity ? 'đã tìm thấy' : 'không tìm thấy'}`, { field, value });
      return entity;
    } catch (error: unknown) {
      logger.error(`Failed to find ${this.modelName} by ${field} / Không thể tìm ${this.modelName} theo ${field}`, { ...formatErrorForLogging(error), field, value });
      throw new DatabaseError(`Failed to find ${this.modelName}`, { field, value, originalError: error });
    }
  }

  /**
   * EN: Find all entities with optional filtering
   * VI: Tìm tất cả entities với filtering tùy chọn
   */
  async findAll(options?: {
    where?: any;
    orderBy?: any;
    skip?: number;
    take?: number;
    include?: any;
  }): Promise<T[]> {
    try {
      logger.debug(`Finding all ${this.modelName} / Tìm tất cả ${this.modelName}`, options);

      const entities = await (this.prisma as any)[this.modelName].findMany(options || {});

      logger.debug(`Found ${entities.length} ${this.modelName} entities / Đã tìm thấy ${entities.length} ${this.modelName} entities`);
      return entities;
    } catch (error: unknown) {
      logger.error(`Failed to find all ${this.modelName} / Không thể tìm tất cả ${this.modelName}`, { ...formatErrorForLogging(error), options });
      throw new DatabaseError(`Failed to find ${this.modelName} entities`, { options, originalError: error });
    }
  }

  /**
   * EN: Create new entity
   * VI: Tạo entity mới
   */
  async create(data: CreateInput): Promise<T> {
    try {
      logger.debug(`Creating new ${this.modelName} / Tạo ${this.modelName} mới`, { data });

      const entity = await (this.prisma as any)[this.modelName].create({
        data,
      });

      logger.debug(`${this.modelName} created successfully / ${this.modelName} đã được tạo thành công`, { id: (entity as any).id });
      return entity;
    } catch (error: unknown) {
      logger.error(`Failed to create ${this.modelName} / Không thể tạo ${this.modelName}`, { ...formatErrorForLogging(error), data });
      throw new DatabaseError(`Failed to create ${this.modelName}`, { data, originalError: error });
    }
  }

  /**
   * EN: Update entity by ID
   * VI: Cập nhật entity theo ID
   */
  async update(id: string, data: UpdateInput): Promise<T> {
    try {
      logger.debug(`Updating ${this.modelName} / Cập nhật ${this.modelName}`, { id, data });

      const entity = await (this.prisma as any)[this.modelName].update({
        where: { id },
        data,
      });

      logger.debug(`${this.modelName} updated successfully / ${this.modelName} đã được cập nhật thành công`, { id });
      return entity;
    } catch (error: unknown) {
      if (hasCode(error) && error.code === 'P2025') {
        logger.warn(`${this.modelName} not found for update / ${this.modelName} không tìm thấy để cập nhật`, { id });
        throw new DatabaseError(`${this.modelName} not found`, { id });
      }
      logger.error(`Failed to update ${this.modelName} / Không thể cập nhật ${this.modelName}`, { ...formatErrorForLogging(error), id, data });
      throw new DatabaseError(`Failed to update ${this.modelName}`, { id, data, originalError: error });
    }
  }

  /**
   * EN: Delete entity by ID
   * VI: Xóa entity theo ID
   */
  async delete(id: string): Promise<boolean> {
    try {
      logger.debug(`Deleting ${this.modelName} / Xóa ${this.modelName}`, { id });

      await (this.prisma as any)[this.modelName].delete({
        where: { id },
      });

      logger.debug(`${this.modelName} deleted successfully / ${this.modelName} đã được xóa thành công`, { id });
      return true;
    } catch (error: unknown) {
      if (hasCode(error) && error.code === 'P2025') {
        logger.warn(`${this.modelName} not found for deletion / ${this.modelName} không tìm thấy để xóa`, { id });
        throw new DatabaseError(`${this.modelName} not found`, { id });
      }
      logger.error(`Failed to delete ${this.modelName} / Không thể xóa ${this.modelName}`, { ...formatErrorForLogging(error), id });
      throw new DatabaseError(`Failed to delete ${this.modelName}`, { id, originalError: error });
    }
  }

  /**
   * EN: Count entities with optional filtering
   * VI: Đếm entities với filtering tùy chọn
   */
  async count(where?: any): Promise<number> {
    try {
      logger.debug(`Counting ${this.modelName} / Đếm ${this.modelName}`, { where });

      const count = await (this.prisma as any)[this.modelName].count({
        where,
      });

      logger.debug(`Counted ${count} ${this.modelName} entities / Đã đếm ${count} ${this.modelName} entities`);
      return count;
    } catch (error: unknown) {
      logger.error(`Failed to count ${this.modelName} / Không thể đếm ${this.modelName}`, { ...formatErrorForLogging(error), where });
      throw new DatabaseError(`Failed to count ${this.modelName}`, { where, originalError: error });
    }
  }

  /**
   * EN: Check if entity exists by ID
   * VI: Kiểm tra entity có tồn tại theo ID
   */
  async exists(id: string): Promise<boolean> {
    try {
      const count = await this.count({ id });
      return count > 0;
    } catch (error: unknown) {
      logger.error(`Failed to check if ${this.modelName} exists / Không thể kiểm tra ${this.modelName} có tồn tại`, { ...formatErrorForLogging(error), id });
      throw error;
    }
  }

  /**
   * EN: Execute transaction with multiple operations
   * VI: Thực thi transaction với nhiều operations
   */
  async transaction<R>(callback: (tx: any) => Promise<R>): Promise<R> {
    try {
      logger.debug(`Starting ${this.modelName} transaction / Bắt đầu transaction ${this.modelName}`);

      const result = await this.prisma.$transaction(async (tx) => {
        return await callback(tx);
      });

      logger.debug(`${this.modelName} transaction completed successfully / Transaction ${this.modelName} đã hoàn thành thành công`);
      return result;
    } catch (error: unknown) {
      logger.error(`${this.modelName} transaction failed / Transaction ${this.modelName} thất bại`, formatErrorForLogging(error));
      throw new DatabaseError(`${this.modelName} transaction failed`, { originalError: error });
    }
  }
}

/**
 * EN: Generic repository interface for type safety
 * VI: Generic repository interface để type safety
 */
export interface IRepository<T, CreateInput, UpdateInput> {
  findById(id: string): Promise<T | null>;
  findByUnique(field: string, value: unknown): Promise<T | null>;
  findAll(options?: any): Promise<T[]>;
  create(data: CreateInput): Promise<T>;
  update(id: string, data: UpdateInput): Promise<T>;
  delete(id: string): Promise<boolean>;
  count(where?: any): Promise<number>;
  exists(id: string): Promise<boolean>;
}