import { router, useForm } from '@inertiajs/react';
import { Copy, CreditCard, Link2, Loader2, Mail, RotateCcw, Wallet } from 'lucide-react';
import { useCallback, useEffect, useState } from 'react';
import { StripePaymentForm } from '@/components/booking/stripe-payment-form';
import { StripeTerminalCollector } from '@/components/booking/stripe-terminal-collector';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Button } from '@/components/ui/button';
import {
    Dialog,
    DialogContent,
    DialogDescription,
    DialogHeader,
    DialogTitle,
} from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import adminBookings from '@/routes/admin/bookings';

type BookingPaymentTarget = {
    id: number;
    uuid: string;
    total: number;
    amount_paid: number;
    payment_status: string;
    stripe_payment_intent_id?: string | null;
};

type PreparePaymentResponse = {
    balance_due_cents: number;
    client_secret: string;
    stripe_publishable_key: string | null;
    payment_url: string;
};

type ChargeMode = 'card' | 'terminal';

type Props = {
    booking: BookingPaymentTarget;
    stripeEnabled: boolean;
    stripePublishableKey: string | null;
    autoOpenCharge?: boolean;
    onUpdated?: () => void;
    subtotalCents?: number | null;
    addonsTotalCents?: number | null;
    collectActionsDisabled?: boolean;
    collectActionsDisabledReason?: string;
};

function formatMoney(cents: number): string {
    return `$${(cents / 100).toFixed(2)}`;
}

export function AdminBookingPaymentActions({
    booking,
    stripeEnabled,
    autoOpenCharge = false,
    onUpdated,
    subtotalCents = null,
    addonsTotalCents = null,
    collectActionsDisabled = false,
    collectActionsDisabledReason,
}: Props) {
    const balanceDueCents = Math.max(0, booking.total - booking.amount_paid);
    const canCollect = balanceDueCents > 0;
    const canRefund =
        booking.amount_paid > 0 &&
        booking.payment_status !== 'refunded' &&
        Boolean(booking.stripe_payment_intent_id);

    const [chargeOpen, setChargeOpen] = useState(false);
    const [refundOpen, setRefundOpen] = useState(false);
    const [prepareError, setPrepareError] = useState<string | null>(null);
    const [preparingMode, setPreparingMode] = useState<ChargeMode | null>(null);
    const [activeChargeTab, setActiveChargeTab] = useState<ChargeMode>('card');
    const [cardPayload, setCardPayload] = useState<PreparePaymentResponse | null>(null);
    const [terminalPayload, setTerminalPayload] = useState<PreparePaymentResponse | null>(null);
    const [copied, setCopied] = useState(false);

    const cashForm = useForm({});
    const linkForm = useForm({});
    const refundForm = useForm({
        amount_cents: booking.amount_paid,
        reason: '',
    });

    const reloadBooking = useCallback(() => {
        onUpdated?.();
        router.reload({ only: ['bookings', 'stats'] });
    }, [onUpdated]);

    const resetChargeState = useCallback(() => {
        setActiveChargeTab('card');
        setCardPayload(null);
        setTerminalPayload(null);
        setPrepareError(null);
        setPreparingMode(null);
    }, []);

    const prepareCharge = useCallback(
        async (mode: ChargeMode): Promise<PreparePaymentResponse | null> => {
            setPreparingMode(mode);
            setPrepareError(null);

            try {
                const url = adminBookings.payment.prepare.url(booking.id, {
                    query: mode === 'terminal' ? { terminal: '1' } : undefined,
                });
                const response = await fetch(url, {
                    headers: { Accept: 'application/json' },
                    credentials: 'same-origin',
                });
                const payload = (await response.json()) as PreparePaymentResponse & {
                    message?: string;
                };

                if (!response.ok) {
                    throw new Error(payload.message ?? 'Could not prepare payment.');
                }

                if (!payload.client_secret || !payload.stripe_publishable_key) {
                    throw new Error('Stripe payment details are unavailable.');
                }

                if (mode === 'card') {
                    setCardPayload(payload);
                } else {
                    setTerminalPayload(payload);
                }

                return payload;
            } catch (error) {
                setPrepareError(
                    error instanceof Error ? error.message : 'Could not prepare payment.',
                );

                return null;
            } finally {
                setPreparingMode(null);
            }
        },
        [booking.id],
    );

    const openChargeDialog = useCallback(async () => {
        resetChargeState();
        setChargeOpen(true);
        await prepareCharge('card');
    }, [prepareCharge, resetChargeState]);

    const handleChargeTabChange = useCallback(
        (value: string) => {
            const mode: ChargeMode = value === 'terminal' ? 'terminal' : 'card';
            setActiveChargeTab(mode);

            const existingPayload = mode === 'card' ? cardPayload : terminalPayload;

            if (!existingPayload) {
                void prepareCharge(mode);
            }
        },
        [cardPayload, terminalPayload, prepareCharge],
    );

    useEffect(() => {
        if (autoOpenCharge && stripeEnabled && canCollect) {
            void openChargeDialog();
        }
    }, [autoOpenCharge, stripeEnabled, canCollect, openChargeDialog]);

    const copyPaymentLink = async () => {
        const paymentUrl = `${window.location.origin}/pay/${booking.uuid}`;

        try {
            await navigator.clipboard.writeText(paymentUrl);
            setCopied(true);
            window.setTimeout(() => setCopied(false), 2000);
        } catch {
            setPrepareError('Could not copy the payment link.');
        }
    };

    const markCashPaid = () => {
        cashForm.post(adminBookings.payment.cash.url(booking.id), {
            preserveScroll: true,
            onSuccess: reloadBooking,
        });
    };

    const sendPaymentLink = () => {
        linkForm.post(adminBookings.payment.sendLink.url(booking.id), {
            preserveScroll: true,
            onSuccess: reloadBooking,
        });
    };

    const submitRefund = () => {
        refundForm.post(adminBookings.payment.refund.url(booking.id), {
            preserveScroll: true,
            onSuccess: () => {
                setRefundOpen(false);
                reloadBooking();
            },
        });
    };

    const activePayload = activeChargeTab === 'terminal' ? terminalPayload : cardPayload;
    const isPreparingActiveTab = preparingMode === activeChargeTab;

    return (
        <div className="flex flex-col gap-3 rounded-2xl border bg-muted/20 p-4">
            {subtotalCents !== null &&
            addonsTotalCents !== null &&
            addonsTotalCents > 0 ? (
                <div className="grid grid-cols-2 gap-3 text-sm">
                    <div>
                        <p className="text-xs text-muted-foreground">Subtotal</p>
                        <p className="font-semibold">{formatMoney(subtotalCents)}</p>
                    </div>
                    <div>
                        <p className="text-xs text-muted-foreground">Add-ons</p>
                        <p className="font-semibold">{formatMoney(addonsTotalCents)}</p>
                    </div>
                </div>
            ) : null}

            <div className="grid grid-cols-3 gap-3 text-sm">
                <div>
                    <p className="text-xs text-muted-foreground">Total</p>
                    <p className="font-semibold">{formatMoney(booking.total)}</p>
                </div>
                <div>
                    <p className="text-xs text-muted-foreground">Paid</p>
                    <p className="font-semibold">{formatMoney(booking.amount_paid)}</p>
                </div>
                <div>
                    <p className="text-xs text-muted-foreground">Balance</p>
                    <p className="font-semibold">{formatMoney(balanceDueCents)}</p>
                </div>
            </div>

            {prepareError ? (
                <Alert variant="destructive">
                    <AlertDescription>{prepareError}</AlertDescription>
                </Alert>
            ) : null}

            {collectActionsDisabled && collectActionsDisabledReason ? (
                <p className="text-xs text-muted-foreground">
                    {collectActionsDisabledReason}
                </p>
            ) : null}

            {canCollect ? (
                <div className="flex flex-wrap gap-2">
                    {stripeEnabled ? (
                        <Button
                            type="button"
                            size="sm"
                            disabled={
                                collectActionsDisabled || preparingMode !== null
                            }
                            onClick={() => void openChargeDialog()}
                        >
                            <CreditCard className="size-4" />
                            {preparingMode !== null ? 'Preparing…' : 'Charge card'}
                        </Button>
                    ) : null}
                    <Button
                        type="button"
                        size="sm"
                        variant="outline"
                        disabled={collectActionsDisabled || cashForm.processing}
                        onClick={markCashPaid}
                    >
                        <Wallet className="size-4" />
                        Mark cash paid
                    </Button>
                    <Button
                        type="button"
                        size="sm"
                        variant="outline"
                        disabled={linkForm.processing}
                        onClick={sendPaymentLink}
                    >
                        <Mail className="size-4" />
                        Email pay link
                    </Button>
                    <Button type="button" size="sm" variant="outline" onClick={() => void copyPaymentLink()}>
                        <Copy className="size-4" />
                        {copied ? 'Copied' : 'Copy pay link'}
                    </Button>
                </div>
            ) : null}

            {canRefund ? (
                <Button
                    type="button"
                    size="sm"
                    variant="outline"
                    className="w-fit"
                    onClick={() => setRefundOpen(true)}
                >
                    <RotateCcw className="size-4" />
                    Refund via Stripe
                </Button>
            ) : null}

            <Dialog
                open={chargeOpen}
                onOpenChange={(open) => {
                    setChargeOpen(open);

                    if (!open) {
                        resetChargeState();
                    }
                }}
            >
                <DialogContent className="max-h-[90vh] overflow-y-auto sm:max-w-lg">
                    <DialogHeader>
                        <DialogTitle>Collect payment</DialogTitle>
                        <DialogDescription>
                            Charge {formatMoney(activePayload?.balance_due_cents ?? balanceDueCents)} for
                            booking #{booking.uuid.slice(0, 8).toUpperCase()}.
                        </DialogDescription>
                    </DialogHeader>

                    <Tabs value={activeChargeTab} onValueChange={handleChargeTabChange}>
                        <TabsList className="grid w-full grid-cols-2">
                            <TabsTrigger value="card">Card / Apple Pay</TabsTrigger>
                            <TabsTrigger value="terminal">Terminal reader</TabsTrigger>
                        </TabsList>

                        <TabsContent value="card" className="mt-4">
                            {isPreparingActiveTab && activeChargeTab === 'card' ? (
                                <div className="flex items-center justify-center gap-2 py-8 text-sm text-muted-foreground">
                                    <Loader2 className="size-4 animate-spin" />
                                    Loading card form…
                                </div>
                            ) : cardPayload?.stripe_publishable_key ? (
                                <StripePaymentForm
                                    key={cardPayload.client_secret}
                                    clientSecret={cardPayload.client_secret}
                                    stripePublishableKey={cardPayload.stripe_publishable_key}
                                    submitLabel={`Charge ${formatMoney(cardPayload.balance_due_cents)}`}
                                    confirmUrl={adminBookings.payment.confirm.url(booking.id)}
                                    onSuccess={() => {
                                        setChargeOpen(false);
                                        resetChargeState();
                                        reloadBooking();
                                    }}
                                />
                            ) : null}
                        </TabsContent>

                        <TabsContent value="terminal" className="mt-4">
                            {isPreparingActiveTab && activeChargeTab === 'terminal' ? (
                                <div className="flex items-center justify-center gap-2 py-8 text-sm text-muted-foreground">
                                    <Loader2 className="size-4 animate-spin" />
                                    Preparing terminal payment…
                                </div>
                            ) : terminalPayload ? (
                                <StripeTerminalCollector
                                    key={terminalPayload.client_secret}
                                    clientSecret={terminalPayload.client_secret}
                                    connectionTokenUrl={adminBookings.terminal.connectionToken.url()}
                                    amountLabel={formatMoney(terminalPayload.balance_due_cents)}
                                    confirmUrl={adminBookings.payment.confirm.url(booking.id)}
                                    onSuccess={() => {
                                        setChargeOpen(false);
                                        resetChargeState();
                                        reloadBooking();
                                    }}
                                />
                            ) : null}
                        </TabsContent>
                    </Tabs>
                </DialogContent>
            </Dialog>

            <Dialog open={refundOpen} onOpenChange={setRefundOpen}>
                <DialogContent className="sm:max-w-md">
                    <DialogHeader>
                        <DialogTitle>Refund payment</DialogTitle>
                        <DialogDescription>
                            Refund up to {formatMoney(booking.amount_paid)} to the customer&apos;s card.
                        </DialogDescription>
                    </DialogHeader>
                    <div className="grid gap-4">
                        <div className="grid gap-2">
                            <Label htmlFor="refund-amount">Amount (cents)</Label>
                            <Input
                                id="refund-amount"
                                type="number"
                                min={1}
                                max={booking.amount_paid}
                                value={refundForm.data.amount_cents}
                                onChange={(event) =>
                                    refundForm.setData(
                                        'amount_cents',
                                        Number(event.target.value),
                                    )
                                }
                            />
                            <p className="text-xs text-muted-foreground">
                                Leave at {booking.amount_paid} for a full refund.
                            </p>
                        </div>
                        <div className="grid gap-2">
                            <Label htmlFor="refund-reason">Reason (optional)</Label>
                            <Input
                                id="refund-reason"
                                value={refundForm.data.reason}
                                onChange={(event) =>
                                    refundForm.setData('reason', event.target.value)
                                }
                            />
                        </div>
                        <Button
                            type="button"
                            variant="destructive"
                            disabled={refundForm.processing}
                            onClick={submitRefund}
                        >
                            {refundForm.processing ? 'Processing…' : 'Process refund'}
                        </Button>
                    </div>
                </DialogContent>
            </Dialog>

            {!stripeEnabled && canCollect ? (
                <p className="flex items-center gap-2 text-xs text-muted-foreground">
                    <Link2 className="size-3.5" />
                    Stripe is not configured. Use cash or send the customer to pay in person.
                </p>
            ) : null}
        </div>
    );
}
