import { Product } from '@/entities/product.entity';
import { PublistHistory } from '@/entities/publist-history.entity';
import CoreService from '@/system/core/core-service';
import { SystemLang } from '@/system/lang/system.lang';
import AppResponse from '@/system/response/ktq-response';
import { urlToBase64 } from '@/ultils/fn';
import {
  BadRequestException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { plainToClass } from 'class-transformer';
import { isURL } from 'class-validator';
import { fromEvent, map, Observable } from 'rxjs';
import { Repository } from 'typeorm';
import { EventsService } from '../events/events.service';
import { MediasService } from '../medias/medias.service';
import { CreateProductDto } from './dtos/create-product.dto';
import { PublistFinishDto } from './dtos/publist-finish.dto';
import { Request } from 'express';
import { SyncDto } from './dtos/syncs.dto';

@Injectable()
export class ProductsService extends CoreService<Product> {
  public static EVENTS = {
    SEND_PUBLIST: 'send-publist',
    SEND_DELETE: 'send-delete',
    PUBLIST_FINISH: 'publist-finish',
    DElETE_FINISH: 'delete-finish',
  };

  constructor(
    @InjectRepository(Product)
    readonly repo: Repository<Product>,
    @InjectRepository(PublistHistory)
    readonly historiesRepo: Repository<PublistHistory>,
    private readonly mediasService: MediasService,
    private readonly eventService: EventsService,
  ) {
    super(
      repo,
      {
        sortableColumns: ['id', 'description', 'title', 'sku', 'created_at'],
        defaultSortBy: [['id', 'DESC']],
        filterableColumns: {
          id: true,
          description: true,
          title: true,
          sku: true,
          created_at: true,
        },
        relations: {
          histories: true,
        },
      },
      Product,
    );
  }

  protected async afterDelete(
    id: number,
    data: Partial<Product>,
    req: Request,
  ): Promise<void> {
    // Xóa images nếu có
    if (data.images?.length) {
      for (const image of data.images) {
        await this.mediasService.removeMediasFolder(
          `${data.title.toLowerCase().replaceAll(' ', '-')}/${image}` as string,
          'products',
        );
      }
    }
  }

  protected async beforeDelete(
    id: number,
    data: Partial<Product>,
    req: Request,
  ): Promise<boolean> {
    // Emit sự kiện để bắt đầu publish
    this.eventService.sendEvent(ProductsService.EVENTS.SEND_DELETE, {
      ...data,
    });

    // Đợi phản hồi từ client
    const result = await this.waitForDeleteResult({ ...data, id });

    if (!result)
      throw new BadRequestException(
        AppResponse.toResponse(false, {
          message: SystemLang.getText('messages', 'try_again'),
        }),
      );

    return true;
  }

  async create(data: CreateProductDto): Promise<any> {
    const product = this.repo.create(data);

    if (data.images && data.images.length) {
      const newImages = [];

      for (const image of data.images) {
        if (image.startsWith('data:')) {
          const result = await this.mediasService.saveBase64Image(
            image,
            'product',
            `medias/products/${data.title.toLowerCase().replaceAll(' ', '-')}`,
          );
          newImages.push(result.filename);
        } else if (
          isURL(image, { require_host: true, require_protocol: true })
        ) {
          const result = await this.mediasService.downloadImageAndSave(
            image,
            'product',
            `medias/products/${data.title.toLowerCase().replaceAll(' ', '-')}`,
          );
          newImages.push(result.filename);
        } else {
          newImages.push(image);
        }
      }

      if (newImages.length) {
        product.images = newImages;
      }
    }

    await this.repo.save(product);

    return AppResponse.toResponse(product);
  }

  async toImagesBase64(product: Product) {
    const images = [];

    for (const image of product.images) {
      const base64 = await urlToBase64(image);

      images.push(base64);
    }

    return images;
  }

  async publist(id: Product['id']) {
    const product = await this.repo.findOne({ where: { id } });

    if (!product) {
      throw new NotFoundException(
        AppResponse.toResponse(null, {
          message: SystemLang.getText('messages', 'not_found'),
        }),
      );
    }

    const plainData = plainToClass(Product, product);
    let base64Image: string[] = [];

    try {
      base64Image = await this.toImagesBase64(plainData);
    } catch (error) {
      console.error('Error converting images to Base64:', error);
      throw new BadRequestException(
        AppResponse.toResponse(null, {
          message: SystemLang.getText('messages', 'server_error'),
        }),
      );
    }

    // Emit sự kiện để bắt đầu publish
    this.eventService.sendEvent(ProductsService.EVENTS.SEND_PUBLIST, {
      ...plainData,
      images: base64Image,
    });

    // Đợi phản hồi từ client
    const publistResult = await this.waitForPublistResult(plainData);

    if (!publistResult.published) {
      throw new BadRequestException(
        AppResponse.toResponse(null, {
          message: SystemLang.getCustomText({
            vi: publistResult.error,
            en: publistResult.error,
          }),
        }),
      );
    }

    await this.repo.update(plainData.id, { status: true });

    return AppResponse.toResponse(plainData);
  }

  /**
   * Chờ event PUBLIST_FINISH và bắt lỗi nếu quá thời gian hoặc lỗi bất ngờ
   */
  private async waitForPublistResult(data: Product): Promise<PublistHistory> {
    try {
      return await this.eventService.waitForEvent<PublistHistory>(
        `${ProductsService.EVENTS.PUBLIST_FINISH}_${data.id}`,
        60000,
      );
    } catch {
      throw new BadRequestException(
        AppResponse.toResponse(null, {
          message: SystemLang.getText('messages', 'try_again'),
        }),
      );
    }
  }

  private async waitForDeleteResult(
    data: Partial<Product>,
  ): Promise<PublistHistory> {
    try {
      return await this.eventService.waitForEvent<PublistHistory>(
        `${ProductsService.EVENTS.DElETE_FINISH}_${data.id}`,
        60000,
      );
    } catch {
      throw new BadRequestException(
        AppResponse.toResponse(null, {
          message: SystemLang.getText('messages', 'try_again'),
        }),
      );
    }
  }

  async publistFinish(id: Product['id'], data: PublistFinishDto) {
    const product = await this.repo.findOne({ where: { id } });

    if (!product)
      throw new NotFoundException(
        AppResponse.toResponse(null, {
          message: SystemLang.getText('messages', 'not_found'),
        }),
      );

    const plainData = plainToClass(Product, product);

    const result = await this.historiesRepo.save({ ...data, product });

    this.eventService.sendEvent(
      `${ProductsService.EVENTS.PUBLIST_FINISH}_${plainData.id}`,
      plainToClass(PublistHistory, result),
    );

    return AppResponse.toResponse(plainData);
  }

  async deleteFinish(id: Product['id'], data: PublistFinishDto) {
    const product = await this.repo.findOne({ where: { id } });

    if (!product)
      throw new NotFoundException(
        AppResponse.toResponse(null, {
          message: SystemLang.getText('messages', 'not_found'),
        }),
      );

    const plainData = plainToClass(Product, product);

    const result = await this.historiesRepo.save({ ...data, product });

    this.eventService.sendEvent(
      `${ProductsService.EVENTS.DElETE_FINISH}_${plainData.id}`,
      plainToClass(PublistHistory, result),
    );

    return AppResponse.toResponse(plainData);
  }

  sendPublistEvents(): Observable<{ data: any }> {
    // Tạo Observable dựa trên sự kiện 'send publist' của eventEmitter
    return fromEvent(
      this.eventService.event,
      ProductsService.EVENTS.SEND_PUBLIST,
    ).pipe(
      map((data) => ({
        data, // gửi data về client
      })),
    );
  }

  sendDeleteEvents(): Observable<{ data: any }> {
    // Tạo Observable dựa trên sự kiện 'send publist' của eventEmitter
    return fromEvent(
      this.eventService.event,
      ProductsService.EVENTS.SEND_DELETE,
    ).pipe(
      map((data) => ({
        data, // gửi data về client
      })),
    );
  }

  async sync({ items }: SyncDto) {
    const incomingTitles = new Set(items.map((d) => d.title));

    // Lấy danh sách sản phẩm hiện tại trong DB
    const products = await this.repo.find({
      select: ['id', 'title', 'status', 'price'],
    });

    const toDisable: Product[] = [];
    const toEnable: { product: Product; price: number }[] = [];
    const toInsert: { title: string; price: number }[] = [];

    const existingMap = new Map(products.map((p) => [p.title, p]));

    // Tìm sản phẩm cần disable
    for (const product of products) {
      if (!incomingTitles.has(product.title) && product.status) {
        toDisable.push(product);
      }
    }

    // Tìm sản phẩm cần enable hoặc insert mới
    for (const item of items) {
      const existing = existingMap.get(item.title);
      if (!existing) {
        toInsert.push(item);
      } else if (!existing.status || existing.price !== item.price) {
        toEnable.push({ product: existing, price: item.price });
      }
    }

    // Batch update disable
    if (toDisable.length) {
      await this.repo
        .createQueryBuilder()
        .update(Product)
        .set({ status: false })
        .whereInIds(toDisable.map((p) => p.id))
        .execute();

      await this.historiesRepo.save(
        toDisable.map((product) => ({
          message: `Product was disabled at: ${new Date().toISOString()}`,
          product,
          published: false,
        })),
      );
    }

    // Update enable + update giá + log
    for (const { product, price } of toEnable) {
      await this.repo.update(product.id, { status: true, price });
    }

    // Insert sản phẩm mới + log
    if (toInsert.length) {
      const newProducts = this.repo.create(
        toInsert.map((item) => ({
          title: item.title,
          price: item.price,
          status: true,
        })),
      );
      await this.repo.save(newProducts);
    }

    return {
      disabled: toDisable.length,
      enabled: toEnable.length,
      inserted: toInsert.length,
    };
  }
}
