import {
  Body,
  Controller,
  Get,
  Param,
  Post,
  Res,
  UnprocessableEntityException,
} from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Public } from 'src/core/guards';
import { TransactionDto } from './dto/booking-order.dto';
import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity';
import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service';
import { TABLE_NAME } from 'src/core/strings/constants/table.constants';
import { MidtransService } from 'src/modules/configuration/midtrans/data/services/midtrans.service';
import { CreateBookingManager } from '../domain/usecases/managers/create-booking.manager';
import * as QRCode from 'qrcode';
import { Gate } from 'src/core/response/domain/decorators/pagination.response';
import { Response } from 'express';
import {
  RescheduleRequestDTO,
  RescheduleVerificationOTP,
} from './dto/reschedule.dto';
import { RescheduleVerificationManager } from '../domain/usecases/managers/reschedule-verification.manager';
import { RescheduleManager } from '../domain/usecases/managers/reschedule.manager';
import { STATUS } from 'src/core/strings/constants/base.constants';
import * as moment from 'moment';
import { WhatsappService } from 'src/services/whatsapp/whatsapp.service';

@ApiTags('Booking Order')
@Controller('v1/booking')
@Public(true)
export class BookingOrderController {
  constructor(
    private createBooking: CreateBookingManager,
    private serviceData: TransactionDataService,
    private midtransService: MidtransService,
    private rescheduleVerification: RescheduleVerificationManager,
    private rescheduleManager: RescheduleManager,
  ) {}

  @Post()
  async create(@Body() data: TransactionDto) {
    const payload: Partial<TransactionEntity> = data;

    this.createBooking.setData(payload as any);
    this.createBooking.setService(
      this.serviceData,
      TABLE_NAME.TRANSACTION,
      this.midtransService,
    );
    await this.createBooking.execute();
    const result = await this.createBooking.getResult();
    const {
      invoice_code,
      status,
      payment_midtrans_token,
      payment_midtrans_url,
      id,
    } = result;

    return {
      id,
      invoice_code,
      status,
      payment_midtrans_token,
      payment_midtrans_url,
    };
  }

  @Post('reschedule')
  async reschedule(@Body() data: RescheduleRequestDTO) {
    const transaction = await this.serviceData.getTransactionWithReschedule(
      data.booking_id,
    );

    const today = moment().startOf('day');
    const rescheduleDate = moment(data.reschedule_date, 'DD-MM-YYYY');
    const rescheduleDateStartOfDay = rescheduleDate.startOf('day');

    //TODO: validate session period priority

    if (rescheduleDateStartOfDay.isSameOrBefore(today)) {
      throw new UnprocessableEntityException(
        'Reschedule date must be in the future',
      );
    }

    if (!transaction) {
      throw new UnprocessableEntityException('Transaction not found');
    }

    if (transaction.status !== STATUS.SETTLED) {
      throw new UnprocessableEntityException('Transaction is not settled');
    }

    if (transaction.children_transactions.length > 0) {
      throw new UnprocessableEntityException('Transaction already rescheduled');
    }

    if (transaction.parent_id) {
      throw new UnprocessableEntityException('Transaction is a reschedule');
    }

    const result = await this.rescheduleVerification.saveVerification(data);
    const maskedPhoneNumber = result.phone_number.replace(/.(?=.{4})/g, '*');
    result.phone_number = maskedPhoneNumber;

    return `Verification code sent to ${maskedPhoneNumber}`;
  }

  @Post('reschedule/verification')
  async verificationReschedule(@Body() data: RescheduleVerificationOTP) {
    const result = await this.rescheduleVerification.verifyOtp(
      data.booking_id,
      +data.code,
    );

    const reschedule = await this.rescheduleManager.reschedule(result);
    const transaction = await this.get(reschedule.id);

    return {
      id: reschedule.id,
      phone_number: result.phone_number,
      name: result.name,
      reschedule_date: result.reschedule_date,
      transaction,
    };
  }

  @Get(':id')
  async get(@Param('id') transactionId: string) {
    const data = await this.serviceData.getOneByOptions({
      relations: [
        'items',
        'parent_transaction',
        'items.item',
        'items.item.time_group',
      ],
      where: { id: transactionId },
    });

    const {
      parent_id,
      customer_name,
      customer_phone,
      booking_date,
      invoice_code,
      status,
      id,
      items,
      parent_transaction,
    } = data;

    let timeGroup = null;

    const usageItems = items.map((item) => {
      const itemData = item.item;
      if (itemData.time_group) {
        const timeGroupData = itemData.time_group;
        const {
          id: groupId,
          name,
          start_time,
          end_time,
          max_usage_time,
        } = timeGroupData;
        timeGroup = {
          id: groupId,
          name,
          start_time,
          end_time,
          max_usage_time,
        };
      }
      const {
        id,
        item_id,
        item_name,
        item_price,
        item_category_name,
        total_price,
        total_net_price,
        qty,
        qty_remaining,
      } = item;
      return {
        id,
        item_id,
        item_name,
        item_price,
        item_category_name,
        total_price,
        total_net_price,
        qty,
        qty_remaining,
      };
    });

    // Mask customer_phone with * and keep last 4 numbers
    let maskedCustomerPhone = customer_phone;
    if (typeof customer_phone === 'string' && customer_phone.length > 4) {
      const last4 = customer_phone.slice(-4);
      maskedCustomerPhone = '*'.repeat(customer_phone.length - 4) + last4;
    }

    let parentTransaction = undefined;
    if (parent_transaction) {
      const {
        id: parentId,
        invoice_code: parentInvoiceCode,
        invoice_date: parentInvoiceDate,
      } = parent_transaction;
      parentTransaction = {
        id: parentId,
        invoice_code: parentInvoiceCode,
        invoice_date: parentInvoiceDate,
      };
    }

    return {
      customer_name,
      customer_phone: maskedCustomerPhone,
      booking_date,
      invoice_code,
      status,
      id,
      is_reschedule: !!parent_id,
      items: usageItems,
      time_group: timeGroup,
      parent: parentTransaction,
    };
  }

  @Gate()
  @Get('qrcode/:id')
  async getQRcode(@Param('id') id: string, @Res() res: Response) {
    console.log(QRCode);
    const qrData = id;
    const data = await QRCode.toDataURL(qrData);
    res.setHeader('Content-Type', 'image/png');
    const base64Data = data.split(',')[1];
    const buffer = Buffer.from(base64Data, 'base64');
    res.send(buffer);
  }

  @Post('resend-notification/:id')
  async resendNotification(@Param('id') id: string) {
    try {
      const transaction = await this.serviceData.getOneByOptions({
        where: { id },
      });

      const whatsappService = new WhatsappService();
      const formattedDate = moment(transaction.booking_date);
      const payload = {
        id: transaction.id,
        phone: transaction.customer_phone,
        code: transaction.invoice_code,
        name: transaction.customer_name,
        time: formattedDate.valueOf(),
      };
      await whatsappService.bookingCreated(payload);
      return {
        message: 'Notification sent successfully',
      };
    } catch (error) {
      throw new UnprocessableEntityException({
        message: 'Failed to send notification',
      });
    }
  }
}
