import {
    BadRequestException,
    Injectable,
    InternalServerErrorException,
    NotFoundException,
} from "@nestjs/common";
import { DataSource } from "typeorm";
import { PaymentEntity } from "./entities/payment.entity";
import { PaymentRepository } from "./payment.repository";

import * as fs from "fs";
import * as path from "path";
import * as Handlebars from "handlebars";
import { ConfigService } from "@nestjs/config";
import { Booking } from "../bookings/entities/booking.entity";

import {
    ClientAction,
    createMerchantOrderId,
    ProviderPaymentStatus,
    TelebirrProvider,
} from "@edr/payment-providers";
import { ProviderInitiationInput } from "@edr/types"
import { InitiateResponseDto, PaymentPlatformDto } from "./payments.dto";

const DEFAULT_CURRENCY = "ETB";

@Injectable()
export class PaymentService {
    constructor(
        private readonly configService: ConfigService,
        private readonly datasource: DataSource,
        private readonly paymentRepo: PaymentRepository,
        private readonly telebirrProvider: TelebirrProvider,
    ) { }

    async initBookingTelebirr(
        bookingId: string,
        platform: PaymentPlatformDto,
    ): Promise<{ redirectUrl: string }> {
        // const booking = await this.datasource.getRepository(Booking).findOneBy({ id: bookingId });
        // if (!booking) throw new NotFoundException("Booking not found");

        // const booking = new Booking()
        // booking.totalAmount = 20
        // booking.id = randomUUID
        const amount = 20
        const merchantOrderId = createMerchantOrderId();
        const redirectBase = this.configService.get<string>("TELEBIRR_SUCCESS_BOOKING_REDIRECT_BASE_URL");
        const redirectUrl = `${redirectBase}/${merchantOrderId}`;
        const amountMinor = Math.round(Number(amount) * 100);

        const input: ProviderInitiationInput = {
            merchantOrderId,
            orderRef: bookingId,
            amountMinor,
            currency: DEFAULT_CURRENCY,
            platform: platform || "web",
            redirectUrl,
        };

        const result = await this.telebirrProvider.initiate(input);

        const payment = await this.paymentRepo.create({
            amount: amount,
            currency: DEFAULT_CURRENCY,
            method: "telebirr",
            refId: bookingId,
            type: "booking",
            merchantOrderId,
            rawInitiation: result.rawInitiation,
            clientAction: result.clientAction as Record<string, unknown>,
            expiresAt: result.expiresAt,
            reason: `Payment for booking`,
        });

        return {
            redirectUrl: `${this.configService.get<string>("TELEBIRR_REDIRECT_BASE_URL")}/${payment.merchantOrderId}`
        }
    }


    async getActivePaymentByOrderIdAndMethod(orderId: string, method: PaymentEntity["method"]): Promise<PaymentEntity | null> {
        return this.paymentRepo.getActivePaymentByOrderIdAndMethod(orderId, method)
    }


    async genReceiptHtml(orderId: string) {
        const payment = await this.paymentRepo.findOneBy({
            merchantOrderId: orderId,
            status: "success"
        })
        if (!payment) {
            throw new BadRequestException()
        }

        const filePath = path.join(__dirname, "templates", "receipt.hbs");
        if (!fs.existsSync(filePath)) {
            throw new InternalServerErrorException()
        }
        const source = fs.readFileSync(filePath, "utf8");
        const template = Handlebars.compile(source);

        const html = template({
            vendorName: "Ethio Djibouti Railway Ticket Booking",
            vendorAddress: "Addis Ababa",
            receiptDate: payment.paidAt,
            paymentMethod: payment?.method,
            subtotal: payment?.amount.toString(),
            total: payment?.amount.toString(),
            currency: payment?.currency,
            reason: payment?.reason
        });

        return html;
    }

    async checkStatusAndUpdate(orderId: string) {
        const resp = await this.paymentRepo.findOneBy({ merchantOrderId: orderId })
        if (!resp) {
            throw new NotFoundException("order id not found")
        }
        const result = await this.telebirrProvider.queryStatus(resp.merchantOrderId)

        if (result.status === ProviderPaymentStatus.SUCCEEDED) {
            await this.datasource.transaction(async (mg) => {
                await mg.update(Booking, { id: resp.refId }, { status: "PAID" })
                await mg.update(PaymentEntity, { id: resp.id }, { status: "success" })
            })
        }
        return {
            status: result.status
        }
    }

    findBookingById(id: string) {
        return this.paymentRepo.findOneBy({ refId: id, type: "booking" })
    }

    formatIntentResponse(intent: PaymentEntity): InitiateResponseDto {
        const clientAction =
            intent.clientAction && typeof intent.clientAction === "object"
                ? (intent.clientAction as unknown as ClientAction)
                : undefined;
        const statusMap: Record<string, ProviderPaymentStatus> = {
            "action-required": ProviderPaymentStatus.REQUIRES_ACTION,
            "processing": ProviderPaymentStatus.PROCESSING,
            "success": ProviderPaymentStatus.SUCCEEDED,
            "failed": ProviderPaymentStatus.FAILED,
            "canceled": ProviderPaymentStatus.CANCELLED,
            "refunded": ProviderPaymentStatus.CANCELLED,
        };
        return {
            intentId: intent.id,
            status: statusMap[intent.status] ?? ProviderPaymentStatus.PROCESSING,
            clientAction,
            merchantOrderId: intent.merchantOrderId ?? undefined,
        };
    }
}
