import { CoreEntity } from '@/entities/core/core-entity';
import { removeFalsy } from '@/ultils/fn';
import {
  BadRequestException,
  HttpStatus,
  NotFoundException,
} from '@nestjs/common';
import { plainToClass } from 'class-transformer';
import { getMetadataStorage, validate } from 'class-validator';
import {
  FilterOperator,
  FilterSuffix,
  paginate,
  Paginated,
  PaginateQuery,
} from 'nestjs-paginate';
import {
  Column,
  RelationSchemaInput,
  SortBy,
} from 'nestjs-paginate/lib/helper';
import {
  FindOptionsWhere,
  getMetadataArgsStorage,
  In,
  Repository,
} from 'typeorm';
import { SystemLang } from '../lang/system.lang';
import AppResponse from '../response/ktq-response';
import { Request } from 'express';

export default abstract class CoreService<E extends CoreEntity> {
  constructor(
    protected repo: Repository<E>,
    protected options: {
      sortableColumns?: Column<E>[];
      searchableColumns?: Column<E>[];
      filterableColumns?: {
        [key in Column<E> | (string & {})]?:
          | (FilterOperator | FilterSuffix)[]
          | true;
      };
      defaultLimit?: number;
      maxLimit?: number;
      defaultSortBy?: SortBy<E>;
      relations?: RelationSchemaInput<E>;
      where?: FindOptionsWhere<E> | FindOptionsWhere<E>[];
      entityRelation?: RelationSchemaInput<E>;
    } = {},
    protected dtoClass?: new () => E,
  ) {}

  private _getAllTargets(entity: Function): Function[] {
    const targets: Function[] = [];
    let current = entity;
    while (current && current !== Function && current !== Object) {
      targets.push(current);
      current = Object.getPrototypeOf(current);
    }
    return targets;
  }

  private _getValidEntityColumns(entity: Function): string[] {
    const targets = this._getAllTargets(entity);
    return getMetadataArgsStorage()
      .columns.filter(
        (col) =>
          typeof col.target === 'function' &&
          targets.includes(col.target as Function),
      )
      .map((col) => col.propertyName);
  }

  private _sanitizeInputByEntity(input: object, entity: Function): object {
    const validColumns = this._getValidEntityColumns(entity);
    return Object.fromEntries(
      Object.entries(input).filter(([key]) => validColumns.includes(key)),
    );
  }

  private _getValidatedProperties(target: Function): string[] {
    const storage = getMetadataStorage();
    const validations = storage.getTargetValidationMetadatas(
      target,
      '',
      false,
      false,
    );

    const properties = new Set<string>();
    for (const v of validations) {
      if (v.propertyName) {
        properties.add(v.propertyName);
      }
    }

    return Array.from(properties);
  }

  // === Abstract Hooks (bắt buộc service con phải override nếu muốn) ===
  protected async beforeUpdate(
    id: E['id'],
    data: Partial<E> | any,
    entity: E,
    req: Request,
  ): Promise<boolean> {
    return true; // default: cho phép update
  }

  protected async afterValidated(
    data: any,
    validated: Partial<E>,
    entity: E,
    req: Request,
  ): Promise<void> {
    return;
  }

  protected async beforeIndex(data: Paginated<E>): Promise<void> {
    return;
  }

  protected async beforeDelete(
    id: E['id'],
    data: Partial<E>,
    req: Request,
  ): Promise<boolean> {
    return true; // default: cho phép delete
  }

  protected async afterUpdate(
    id: E['id'],
    dataRequest: any,
    dataUpdate: Partial<E>,
    entity: E,
    req: Request,
  ): Promise<void> {
    return;
  }

  protected async afterDelete(
    id: E['id'],
    data: Partial<E>,
    req: Request,
  ): Promise<void> {
    return;
  }

  protected async afterBulkDelete(
    ids: E['id'][],
    data: E[],
    req?: Request,
  ): Promise<void> {
    return;
  }

  protected async beforeBulkDelete(
    ids: E['id'][],
    data: E[],
    req?: Request,
  ): Promise<boolean> {
    return true; // default: cho phép delete
  }

  async validateInput<T>(input: object, dtoClass: new () => T): Promise<T> {
    // Lấy danh sách các field là column trong entity
    const validFields = this._getValidEntityColumns(dtoClass);

    // Lọc input, chỉ giữ field có trong validFields
    const sanitizedInput = Object.fromEntries(
      Object.entries(input).filter(([key]) => validFields.includes(key)),
    );

    // Convert thành class
    const instance = plainToClass(dtoClass, sanitizedInput);

    // Validate
    const errors = await validate(instance as any);

    if (errors.length > 0) {
      const errorMapping = errors.map((error) => {
        return {
          field: error.property,
          errors: Object.values(error.constraints),
        };
      });

      throw new BadRequestException(
        AppResponse.toResponse(null, {
          message: 'Validation failed',
          bonus: { errors: errorMapping },
        }),
      );
    }

    return instance;
  }

  async getById(id: E['id'], relations?: RelationSchemaInput<E>) {
    const data = await this.repo.findOne({
      where: { id: id as any },
      relations: relations || this.options.relations,
    });

    if (!data) {
      throw new NotFoundException(
        AppResponse.toResponse(false, {
          message: SystemLang.getText('messages', 'no_data'),
          status_code: HttpStatus.NOT_FOUND,
        }),
      );
    }

    return AppResponse.toResponse(plainToClass(this.dtoClass, data));
  }

  async create(dto: any) {
    return AppResponse.toResponse(null);
  }

  async index(query: PaginateQuery) {
    const { ...options } = this.options;

    const data = await paginate(query, this.repo, {
      sortableColumns: [],
      searchableColumns: [],
      defaultLimit: 10,
      defaultSortBy: [['created_at', 'DESC'] as any],
      maxLimit: 100,
      ...removeFalsy(options),
    });

    await this.beforeIndex?.(data);

    return AppResponse.toPagination<E>(data, true, this.dtoClass);
  }

  async update(id: E['id'], data: Partial<E> | any, request: Request) {
    // 1. Tìm entity theo ID
    const entity = await this.repo.findOne({
      where: { id: id as any },
      relations: this.options.entityRelation,
    });
    if (!entity) {
      throw new NotFoundException(
        AppResponse.toResponse(false, {
          message: SystemLang.getText('messages', 'no_data'),
          status_code: HttpStatus.NOT_FOUND,
        }),
      );
    }

    // Kiểm tra nếu hàm prev handle return true mới được được đi tiếp
    if (!(await this.beforeUpdate(id, data, entity, request))) return;

    // 2. Kiểm tra DTO class đã được khai báo
    if (!this.dtoClass) {
      throw new Error('dtoClass is required for validation');
    }

    // 3. Merge data mới với entity gốc
    const merged = { ...entity, ...data };

    // 4. Sanitize: loại bỏ các field không phải column trong entity
    const sanitized = this._sanitizeInputByEntity(
      merged,
      this.repo.target as Function,
    );

    const targetUpdate = this._getValidatedProperties(this.dtoClass);

    // 5. Validate
    const validated = await this.validateInput(sanitized, this.dtoClass);

    const newData = targetUpdate.reduce((prev, cur) => {
      prev[cur] = sanitized[cur];

      return prev;
    }, {} as Partial<E>);

    await this.afterValidated(data, newData, entity, request);

    // 6. Update DB
    await this.repo.update(id, newData as any);

    await this.afterUpdate(id, data, newData, entity, request);

    // 7. Trả kết quả
    return AppResponse.toResponse(plainToClass(this.dtoClass, validated), {
      message: SystemLang.getText('messages', 'update_success'),
    });
  }

  async updateWithoutValidation(
    id: E['id'],
    data: Partial<E> | any,
    request: Request,
  ) {
    // 1. Tìm entity theo ID
    const entity = await this.repo.findOne({
      where: { id: id as any },
      relations: this.options.entityRelation,
    });
    if (!entity) {
      throw new NotFoundException(
        AppResponse.toResponse(false, {
          message: SystemLang.getText('messages', 'no_data'),
          status_code: HttpStatus.NOT_FOUND,
        }),
      );
    }

    // Kiểm tra nếu hàm prev handle return true mới được được đi tiếp
    if (!(await this.beforeUpdate(id, data, entity, request))) return;

    // 2. Sanitize: loại bỏ các field không phải column trong entity
    const sanitized = this._sanitizeInputByEntity(
      { ...entity, ...data },
      this.repo.target as Function,
    );

    // 3. Gọi hook sau khi "giả sử" đã validate xong
    await this.afterValidated(data, sanitized, entity, request);

    // 4. Update DB
    await this.repo.update(id, sanitized as any);

    await this.afterUpdate(id, data, sanitized, entity, request);

    // 5. Trả kết quả
    return AppResponse.toResponse(plainToClass(this.dtoClass, sanitized), {
      message: SystemLang.getText('messages', 'update_success'),
    });
  }

  async bulkUpdate(data: Partial<E>[] | any[], request?: Request) {
    // 1. Kiểm tra dữ liệu đầu vào có tồn tại không
    if (!data || data.length === 0) {
      throw new BadRequestException(
        AppResponse.toResponse(false, {
          message: SystemLang.getText('messages', 'no_data'),
          status_code: HttpStatus.BAD_REQUEST,
        }),
      );
    }

    // 2. Lấy danh sách các cột hợp lệ trong entity (column thực sự trong DB)
    const validColumns = this._getValidEntityColumns(
      this.repo.target as Function,
    );

    // 3. Lấy danh sách các field có khai báo class-validator decorator trong DTO
    const targetUpdate = this._getValidatedProperties(this.dtoClass);

    // 4. Duyệt qua từng phần tử để xử lý cập nhật song song
    const updates = await Promise.all(
      data.map(async (item: any) => {
        // 4.1. Bỏ qua nếu không có `id` (không thể cập nhật)
        if (!item.id) return null;

        const { id, ...rest } = item;

        const entity = await this.repo.findOne({
          where: { id: id as any },
          relations: this.options.entityRelation,
        });

        // Kiểm tra nếu hàm prev handle return true mới được được đi tiếp
        if (!(await this.beforeUpdate(id, item, entity, request))) return;

        // 4.2. Chỉ giữ lại các field là column thật trong entity
        const sanitized = Object.fromEntries(
          Object.entries(rest).filter(([key]) => validColumns.includes(key)),
        );

        // 4.3. Thực hiện validate dữ liệu đã được lọc
        const validated = await this.validateInput(sanitized, this.dtoClass);

        // 4.4. Chỉ lấy các field được phép validate để update
        const newData = targetUpdate.reduce((prev, cur) => {
          prev[cur] = sanitized[cur];
          return prev;
        }, {} as Partial<E>);

        await this.afterValidated(data, newData, entity, request);

        // 4.5. Gọi update trên repository
        const result = await this.repo.update(id, newData as any);

        await this.afterUpdate(id, rest, newData, entity, request);

        return result;
      }),
    );

    // 5. Đếm số lượng bản ghi được cập nhật thành công
    const affectedCount = updates.filter(
      (u) => u?.affected && u.affected > 0,
    ).length;

    // 6. Nếu không có bản ghi nào được cập nhật, ném lỗi NotFound
    if (affectedCount === 0) {
      throw new NotFoundException(
        AppResponse.toResponse(false, {
          message: SystemLang.getText('messages', 'no_data'),
          status_code: HttpStatus.NOT_FOUND,
        }),
      );
    }

    // 7. Trả về phản hồi thành công
    return AppResponse.toResponse(true, {
      message: SystemLang.getText('messages', 'update_success'),
    });
  }

  async bulkUpdateWithoutValidation(
    data: Partial<E>[] | any[],
    request?: Request,
  ) {
    // 1. Kiểm tra dữ liệu đầu vào có tồn tại không
    if (!data || data.length === 0) {
      throw new BadRequestException(
        AppResponse.toResponse(false, {
          message: SystemLang.getText('messages', 'no_data'),
          status_code: HttpStatus.BAD_REQUEST,
        }),
      );
    }

    // 2. Lấy danh sách các cột hợp lệ trong entity (column thực sự trong DB)
    const validColumns = this._getValidEntityColumns(
      this.repo.target as Function,
    );

    // 3. Duyệt qua từng phần tử để xử lý cập nhật song song
    const updates = await Promise.all(
      data.map(async (item: any) => {
        // 3.1. Bỏ qua nếu không có `id` (không thể cập nhật)
        if (!item.id) return null;

        const { id, ...rest } = item;

        const entity = await this.repo.findOne({
          where: { id: id as any },
          relations: this.options.entityRelation,
        });

        // Kiểm tra nếu hàm prev handle return true mới được được đi tiếp
        if (!(await this.beforeUpdate(id, item, entity, request))) return;

        // 3.2. Chỉ giữ lại các field là column thật trong entity
        const sanitized = this._sanitizeInputByEntity(
          { ...entity, ...data },
          this.repo.target as Function,
        );

        // 3.3. Gọi hook afterValidated (giả định dữ liệu đã hợp lệ)
        await this.afterValidated(data, sanitized, entity, request);

        // 3.4. Gọi update trên repository
        const result = await this.repo.update(id, sanitized as any);

        await this.afterUpdate(id, rest, sanitized, entity, request);

        return result;
      }),
    );

    // 4. Đếm số lượng bản ghi được cập nhật thành công
    const affectedCount = updates.filter(
      (u) => u?.affected && u.affected > 0,
    ).length;

    // 5. Nếu không có bản ghi nào được cập nhật, ném lỗi NotFound
    if (affectedCount === 0) {
      throw new NotFoundException(
        AppResponse.toResponse(false, {
          message: SystemLang.getText('messages', 'no_data'),
          status_code: HttpStatus.NOT_FOUND,
        }),
      );
    }

    // 6. Trả về phản hồi thành công
    return AppResponse.toResponse(true, {
      message: SystemLang.getText('messages', 'update_success'),
    });
  }

  async delete(id: E['id'], request: Request) {
    const data = await this.repo.findOne({
      where: { id: id as any },
      relations: this.options.entityRelation,
    });
    if (!data)
      throw new NotFoundException(
        AppResponse.toResponse(false, {
          message: SystemLang.getText('messages', 'no_data'),
          status_code: HttpStatus.NOT_FOUND,
        }),
      );

    // Kiểm tra nếu hàm prev handle return true mới được được đi tiếp
    if (!(await this.beforeDelete(id, data, request))) return;

    await this.repo.delete(id);

    // Gọi hàm afterDelete nếu có
    await this.afterDelete(id, data, request);

    return AppResponse.toResponse(true, { message: 'Delete success!' });
  }

  async bulkDelete(ids: E['id'][], request?: Request) {
    if (!ids || ids.length === 0) {
      throw new BadRequestException(
        AppResponse.toResponse(false, {
          message: SystemLang.getText('messages', 'no_data'),
          status_code: HttpStatus.BAD_REQUEST,
        }),
      );
    }

    const data = await this.repo.find({
      where: { id: In(ids) as any },
      relations: this.options.entityRelation,
    });

    // Kiểm tra nếu hàm prev handle return true mới được được đi tiếp
    if (!(await this.beforeBulkDelete(ids, data, request))) return;

    // Xóa
    const result = await this.repo.delete({ id: In(ids) as any });

    if (!result.affected) {
      throw new NotFoundException(
        AppResponse.toResponse(false, {
          message: SystemLang.getText('messages', 'no_data'),
          status_code: HttpStatus.NOT_FOUND,
        }),
      );
    }

    // Gọi hàm afterBulkDelete nếu có
    await this.afterBulkDelete(ids, data, request);

    return AppResponse.toResponse(true, {
      message: SystemLang.getText('messages', 'delete_success'),
    });
  }
}
