import { BadRequestException, Injectable, NotFoundException, StreamableFile } from '@nestjs/common';
import { CreatePointAndTradDto } from './dto/create-pointAndTraduction.dto';
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { PointOfInterest } from './entities/PointOfInterest.entity';
import { DataSource, Repository } from 'typeorm';
import { PointOfInterestTraduction } from './entities/PointOfInterestTraduction.entity';
import { CreatePointDto } from './dto/create-point.dto';
import { PlaceService } from 'src/place/place.service';
import { CreatePointTradDto } from './dto/create-pointTrad.dto';
import { LANGUAGES } from 'src/shared/enum/languages.enum';
import { getPointDto } from './dto/getPoint.dto';
import { ServerConstants } from 'src/constants/server.contants';
import { createReadStream, readFileSync } from 'fs';
import { join } from 'path';
import { generateQRCode } from './utils/qrcode';
import { Place } from 'src/place/entities/place.entity';
import puppeteer from 'puppeteer';
import { printPointInfo } from './dto/printPointInfo.dto';

@Injectable()
export class PointOfInterestService {
  constructor(
    @InjectRepository(PointOfInterest) private pointRepository: Repository<PointOfInterest>,
    @InjectRepository(PointOfInterestTraduction) private pointTraductionRepository,
    private readonly placeService: PlaceService,
    @InjectRepository(Place) private placeRepository: Repository<Place>,
    @InjectDataSource() private dataSource: DataSource,
  ) {}
  async create(createPointAndTradDto: CreatePointAndTradDto) {
    const placeRelation = await this.placeRepository.findOne({
      where: { idPlace: createPointAndTradDto.idPlace },
      relations: ['town'],
    });

    const place = await this.placeService.findOne(createPointAndTradDto.idPlace);
    if (!place) {
      throw new BadRequestException('Place not found');
    }
    const createPointDto: CreatePointDto = {
      name: createPointAndTradDto.name,
      imageName: createPointAndTradDto.image,
      idPlace: place,
    };
    const insertedId = (await this.pointRepository.insert(createPointDto)).raw.insertId;

    const createTradEn: CreatePointTradDto = {
      idPoint: insertedId,
      language: LANGUAGES.EN,
      content: createPointAndTradDto.contentEN,
      audioName: 'default.mp3',
      directions: createPointAndTradDto.directionsEN,
    };

    const createTradEs: CreatePointTradDto = {
      idPoint: insertedId,
      language: LANGUAGES.ES,
      content: createPointAndTradDto.contentES,
      audioName: 'default.mp3',
      directions: createPointAndTradDto.directionsES,
    };
    await this.pointTraductionRepository.insert(createTradEs);
    await this.pointTraductionRepository.insert(createTradEn);
    generateQRCode(placeRelation.town.state.stateId, placeRelation.town.townId, placeRelation.idPlace, insertedId);
  }

  async findAllByPlace(idPlace: number, lang: LANGUAGES): Promise<getPointDto[]> {
    const place = await this.placeService.findOne(idPlace);
    if (!place) {
      throw new BadRequestException('Place not found');
    }
    let points: getPointDto[] = await this.dataSource
      .getRepository(PointOfInterestTraduction)
      .createQueryBuilder('pointTrad')
      .leftJoin('pointTrad.idPoint', 'point')
      .select([
        'point.idPoint as idPoint',
        'point.name as name',
        'point.imageName as imageName',
        'pointTrad.content as content',
        'pointTrad.directions as directions',
        'pointTrad.audioName as audioName',
        'point.idPlace as idPlace',
      ])
      .where('point.idPlace = :idPlace', { idPlace })
      .andWhere('pointTrad.language = :lang', { lang })
      .getRawMany();

    points = points.map((point) => {
      return {
        idPoint: point.idPoint,
        idPlace: point.idPlace,
        name: point.name,
        imageName: `${ServerConstants.HOST}/points/${point.imageName}`,
        content: point.content,
        directions: point.directions,
      };
    });

    return points;
  }

  async getAudio(idPoint: number, lang: LANGUAGES): Promise<StreamableFile> {
    const pointTrad: PointOfInterestTraduction = await this.pointTraductionRepository.findOneBy({
      idPoint,
      language: lang,
    });
    if (!pointTrad) {
      throw new NotFoundException('Point not found');
    }
    const filePath = join(ServerConstants.ROOT_STATIC_PATH, 'audios', pointTrad.audioName);
    const fileStream = createReadStream(filePath);
    return new StreamableFile(fileStream);
  }

  async createAudio() {}

  async findOne(idPoint: number, lang: LANGUAGES): Promise<getPointDto> {
    const pointTrad = await this.dataSource
      .getRepository(PointOfInterestTraduction)
      .createQueryBuilder('pointTrad')
      .leftJoin('pointTrad.idPoint', 'point')
      .select([
        'point.idPoint as idPoint',
        'point.name as name',
        'point.imageName as imageName',
        'pointTrad.content as content',
        'pointTrad.directions as directions',
        'pointTrad.audioName as audioName',
        'point.idPlace as idPlace',
      ])
      .where('point.idPoint = :idPoint', { idPoint })
      .andWhere('pointTrad.language = :lang', { lang })
      .getRawOne();
    if (!pointTrad) {
      throw new NotFoundException('Point not found');
    }

    const point: getPointDto = {
      idPoint: pointTrad.idPoint,
      idPlace: pointTrad.idPlace,
      name: pointTrad.name,
      imageName: `${ServerConstants.HOST}/points/${pointTrad.imageName}`,
      content: pointTrad.content,
      directions: pointTrad.directions,
    };
    return point;
  }

  async findAllByIds(idPlace: number, pointsId: number[]): Promise<printPointInfo[]> {
    const place = await this.placeService.findOne(idPlace);
    if (!place) {
      throw new BadRequestException('Place not found');
    }
    const points: printPointInfo[] = await Promise.all(
      pointsId.map(async (idPoint) => {
        const point = await this.dataSource
          .getRepository(PointOfInterest)
          .createQueryBuilder('point')
          .leftJoin('point.idPlace', 'place')
          .select(['point.idPoint as idPoint', 'place.name as namePlace', 'point.name as name'])
          .where('place.idPlace = :idPlace', { idPlace })
          .andWhere('point.idPoint = :idPoint', { idPoint })
          .getRawOne();
        return point;
      }),
    );
    return points;
  }

  async generatePdf(idPlace: number, pointsId: number[]): Promise<Buffer> {
    const points = await this.findAllByIds(idPlace, pointsId);
    const htmlContent = this.generateHtml(points);
    const browser = await puppeteer.launch();
    const page = await browser.newPage();

    await page.setContent(htmlContent);
    const pdfBuffer = await page.pdf({
      format: 'A4',
      margin: { top: '10mm', bottom: '10mm', left: '10mm', right: '10mm' }, // Márgenes
    });

    await browser.close();

    return Buffer.from(pdfBuffer);
  }

  private generateHtml(points: printPointInfo[]): string {
    let cardsHtml = '';

    for (const point of points) {
      const filePath = join(ServerConstants.ROOT_STATIC_PATH, 'qr', point.idPoint.toString() + '.png');

      cardsHtml += `
        <div class="card">
          <img class="background" src="data:image/jpeg;base64,${readFileSync(
            './src/pointOfInterest/utils/zac.jpeg',
          ).toString('base64')}"/>
          <div class="content">
            <h3>${point.namePlace}</h3>
            <p>${point.name}</p>
          </div>
          <img src="data:image/jpeg;base64,${readFileSync(filePath).toString('base64')}" class="qr-code"/>
        </div>
      `;
    }

    return `
      <!DOCTYPE html>
      <html>
      <head>
        <title>Tarjetas en PDF</title>
        <style>
          body {
            font-family: Arial, sans-serif;
          }
          .card {
            width: 16cm;
            height: 8cm;
            page-break-inside: avoid;
            position: relative;
          }

          .content{
            margin: 2cm;
          }

          .qr-code{
            width: 4cm;
            height: 4cm;
            position: absolute;
            right: 1.5cm;
            top: 0;
            bottom: 0;
            margin: auto 0;
            border: 3px solid black;
            border-radius: 8px;
          }

          .background {
            height: 100%;
            width: 100%;
            object-fit: contain;
            position: absolute;
            z-index: -1;
          }

          @page {
            size: A4;
            margin: 10mm;
          }
          .page {
            display: flex;
            flex-wrap: wrap;
            justify-content: space-between;
            height: calc(100vh - 20mm);
          }
        </style>
      </head>
      <body>
        <div class="page">
          ${cardsHtml}
        </div>
      </body>
      </html>
    `;
  }
}
