import {
    Body,
    Controller,
    Get,
    HttpStatus,
    Post,
    Query,
    Res,
} from "@nestjs/common";
import {
    ApiTags,
    ApiOperation,
    ApiQuery,
    ApiOkResponse,
    ApiProduces,
} from "@nestjs/swagger";
import { Response } from "express";
import { Public } from "@edr/api-common";

import { BillingService } from "./billing.service";
import {
    InitiatePaymentDto,
    InitiateResponseDto,
    PaymentMethodTypeEnum,
    PaymentPlatformDto,
} from "../payment/payments.dto";

/**
 * Central payment entrypoints. Domain-agnostic — the caller supplies an
 * invoice ID and the billing service resolves the amount and drives the
 * gateway. The domain never talks to the payment service directly.
 * Routes are unchanged (`/payments/*`) so the portal is unaffected.
 */
@ApiTags("Payment")
@Controller("payments")
export class PaymentController {
    constructor(private readonly billing: BillingService) { }

    @Post("initiate")
    @ApiOperation({
        summary: "Initiate payment for an invoice",
        description: "Charges the invoice through the payment gateway.",
    })
    @ApiOkResponse({ type: InitiateResponseDto })
    initiate(@Body() dto: InitiatePaymentDto): Promise<InitiateResponseDto> {
        return this.billing.payInvoice(dto.invoiceId, {
            method: dto.method,
            platform: dto.platform,
            payerAccount: dto.payerAccount,
            returnUrl: dto.returnUrl,
            failureUrl: dto.failureUrl,
        });
    }

    @Get("checkout")
    @Public()
    @ApiOperation({
        summary: "Browser checkout redirect",
        description:
            "Charges the invoice and returns an HTML page that auto-redirects to the provider checkout URL. Open directly in a browser tab.",
    })
    @ApiQuery({ name: "invoiceId", required: true })
    @ApiQuery({ name: "method", enum: PaymentMethodTypeEnum, required: true })
    @ApiQuery({ name: "platform", enum: ["web", "mobile"], required: false })
    @ApiProduces("text/html")
    async checkout(
        @Query("invoiceId") invoiceId: string,
        @Query("method") method: PaymentMethodTypeEnum,
        @Query("platform") platform: PaymentPlatformDto = "web",
        @Res() res: Response,
    ) {
        if (!invoiceId) {
            return res
                .status(HttpStatus.BAD_REQUEST)
                .type("html")
                .send(this.buildErrorHtml("Missing required query parameter: invoiceId"));
        }
        if (!method || !Object.values(PaymentMethodTypeEnum).includes(method)) {
            return res
                .status(HttpStatus.BAD_REQUEST)
                .type("html")
                .send(this.buildErrorHtml("Missing or invalid query parameter: method"));
        }

        try {
            const result = await this.billing.payInvoice(
                invoiceId,
                { method, platform },
            );
            const url =
                result.clientAction?.type === "REDIRECT" ? result.clientAction.url : undefined;

            if (url) {
                return res.status(HttpStatus.OK).type("html").send(this.buildRedirectHtml(url));
            }
            return res
                .status(HttpStatus.OK)
                .type("html")
                .send(this.buildStatusHtml(result.status, result.intentId));
        } catch (err: unknown) {
            const message = err instanceof Error ? err.message : "An unexpected error occurred";
            return res.status(HttpStatus.OK).type("html").send(this.buildErrorHtml(message));
        }
    }

    /**
     * HTML-escape a value interpolated into the public checkout pages. These
     * pages are served unauthenticated and the interpolated values (provider
     * error messages, status strings, intent ids, redirect URLs) can carry
     * attacker-influenced input — unescaped they are a reflected-XSS sink.
     */
    private escapeHtml(value: string): string {
        return value
            .replace(/&/g, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;")
            .replace(/"/g, "&quot;")
            .replace(/'/g, "&#39;");
    }

    private buildRedirectHtml(url: string): string {
        // Only http(s) URLs may be used as a redirect target — a javascript:
        // URL would execute in the victim's browser from the <a>/location.href.
        let parsed: URL;
        try {
            parsed = new URL(url);
        } catch {
            return this.buildErrorHtml("Invalid payment redirect URL");
        }
        if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
            return this.buildErrorHtml("Invalid payment redirect URL");
        }
        const escaped = this.escapeHtml(url);
        const jsEscaped = JSON.stringify(url);
        return `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="refresh" content="0;url=${escaped}">
  <title>Redirecting to payment…</title>
  <style>
    body { font-family: sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #f5f5f5; }
    .card { background: #fff; border-radius: 8px; padding: 40px; text-align: center; box-shadow: 0 2px 8px rgba(0,0,0,.1); max-width: 400px; }
    .spinner { width: 40px; height: 40px; border: 4px solid #e0e0e0; border-top-color: #1a73e8; border-radius: 50%; animation: spin .8s linear infinite; margin: 0 auto 20px; }
    @keyframes spin { to { transform: rotate(360deg); } }
    p { color: #555; margin: 0 0 16px; }
    a { color: #1a73e8; }
  </style>
</head>
<body>
  <div class="card">
    <div class="spinner"></div>
    <p>Redirecting to payment provider…</p>
    <p><a href="${escaped}">Click here if you are not redirected</a></p>
  </div>
  <script>window.location.href = ${jsEscaped};</script>
</body>
</html>`;
    }

    private buildStatusHtml(rawStatus: string, rawIntentId: string): string {
        const status = this.escapeHtml(rawStatus);
        const intentId = this.escapeHtml(rawIntentId);
        return `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Payment status</title>
  <style>
    body { font-family: sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #f5f5f5; }
    .card { background: #fff; border-radius: 8px; padding: 40px; text-align: center; box-shadow: 0 2px 8px rgba(0,0,0,.1); max-width: 400px; }
    .status { font-size: 1.1rem; font-weight: 600; color: #333; margin-bottom: 8px; }
    small { color: #888; }
  </style>
</head>
<body>
  <div class="card">
    <div class="status">${status}</div>
    <small>Intent: ${intentId}</small>
  </div>
</body>
</html>`;
    }

    private buildErrorHtml(rawMessage: string): string {
        const message = this.escapeHtml(rawMessage);
        return `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Payment error</title>
  <style>
    body { font-family: sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #f5f5f5; }
    .card { background: #fff; border-radius: 8px; padding: 40px; text-align: center; box-shadow: 0 2px 8px rgba(0,0,0,.1); max-width: 400px; }
    .error { color: #d32f2f; font-weight: 600; margin-bottom: 8px; }
    p { color: #555; }
  </style>
</head>
<body>
  <div class="card">
    <div class="error">Payment could not be initiated</div>
    <p>${message}</p>
  </div>
</body>
</html>`;
    }
}
