import { loadStripeTerminal } from '@stripe/terminal-js';
import { useCallback, useEffect, useRef, useState } from 'react';
import { syncBookingPayment } from '@/lib/sync-booking-payment';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Button } from '@/components/ui/button';
import { NativeSelect } from '@/components/ui/native-select';

type ReaderOption = {
    id: string;
    label: string;
};

type Props = {
    clientSecret: string;
    connectionTokenUrl: string;
    amountLabel: string;
    confirmUrl?: string;
    onSuccess?: () => void;
};

type TerminalInstance = {
    discoverReaders: (config: { simulated: boolean }) => Promise<{ discoveredReaders: Array<{ id: string; label?: string }> }>;
    connectReader: (reader: { id: string }) => Promise<unknown>;
    collectPaymentMethod: (clientSecret: string) => Promise<{ paymentIntent: { id: string } }>;
    processPayment: (paymentIntent: { id: string }) => Promise<unknown>;
    disconnectReader: () => Promise<unknown>;
};

export function StripeTerminalCollector({
    clientSecret,
    connectionTokenUrl,
    amountLabel,
    confirmUrl,
    onSuccess,
}: Props) {
    const terminalRef = useRef<TerminalInstance | null>(null);
    const [readers, setReaders] = useState<ReaderOption[]>([]);
    const [selectedReaderId, setSelectedReaderId] = useState('');
    const [status, setStatus] = useState<string | null>(null);
    const [error, setError] = useState<string | null>(null);
    const [processing, setProcessing] = useState(false);
    const [connected, setConnected] = useState(false);

    const fetchConnectionToken = useCallback(async (): Promise<string> => {
        const response = await fetch(connectionTokenUrl, {
            headers: { Accept: 'application/json' },
            credentials: 'same-origin',
        });

        if (!response.ok) {
            throw new Error('Could not fetch Terminal connection token.');
        }

        const payload = (await response.json()) as { secret?: string };

        if (!payload.secret) {
            throw new Error('Terminal connection token is missing.');
        }

        return payload.secret;
    }, [connectionTokenUrl]);

    useEffect(() => {
        let cancelled = false;

        const init = async () => {
            const StripeTerminal = await loadStripeTerminal();

            if (cancelled || !StripeTerminal) {
                return;
            }

            const terminal = StripeTerminal.create({
                onFetchConnectionToken: fetchConnectionToken,
                onUnexpectedReaderDisconnect: () => {
                    setConnected(false);
                    setStatus('Reader disconnected.');
                },
            });

            terminalRef.current = terminal as unknown as TerminalInstance;
        };

        void init();

        return () => {
            cancelled = true;
        };
    }, [fetchConnectionToken]);

    const discoverReaders = async () => {
        setError(null);
        setStatus('Searching for readers…');

        const terminal = terminalRef.current;

        if (!terminal) {
            setError('Terminal is still loading.');

            return;
        }

        try {
            const discovery = await terminal.discoverReaders({ simulated: import.meta.env.DEV });
            const options = discovery.discoveredReaders.map((reader) => ({
                id: reader.id,
                label: reader.label ?? reader.id,
            }));

            setReaders(options);
            setSelectedReaderId(options[0]?.id ?? '');
            setStatus(options.length > 0 ? 'Readers found.' : 'No readers found. Use simulated mode in test.');
        } catch (discoveryError) {
            setError(
                discoveryError instanceof Error
                    ? discoveryError.message
                    : 'Could not discover readers.',
            );
            setStatus(null);
        }
    };

    const connectReader = async () => {
        const terminal = terminalRef.current;
        const reader = readers.find((item) => item.id === selectedReaderId);

        if (!terminal || !reader) {
            setError('Select a reader first.');

            return;
        }

        setError(null);
        setStatus('Connecting to reader…');

        try {
            await terminal.connectReader({ id: reader.id });
            setConnected(true);
            setStatus(`Connected to ${reader.label}.`);
        } catch (connectError) {
            setError(
                connectError instanceof Error
                    ? connectError.message
                    : 'Could not connect to reader.',
            );
            setStatus(null);
        }
    };

    const collectPayment = async () => {
        const terminal = terminalRef.current;

        if (!terminal || !connected) {
            setError('Connect a reader before collecting payment.');

            return;
        }

        setProcessing(true);
        setError(null);
        setStatus('Present card on reader…');

        try {
            const collected = await terminal.collectPaymentMethod(clientSecret);
            setStatus('Processing payment…');
            await terminal.processPayment(collected.paymentIntent);
            setStatus('Payment successful.');

            if (confirmUrl) {
                await syncBookingPayment(confirmUrl);
            }

            onSuccess?.();
        } catch (paymentError) {
            setError(
                paymentError instanceof Error
                    ? paymentError.message
                    : 'Terminal payment failed.',
            );
            setStatus(null);
        } finally {
            setProcessing(false);
        }
    };

    return (
        <div className="grid gap-4">
            <p className="text-sm text-muted-foreground">
                Use a Stripe Terminal reader for in-person tap or insert. Collect {amountLabel}.
            </p>

            <div className="flex flex-col gap-3 sm:flex-row">
                <Button type="button" variant="outline" onClick={() => void discoverReaders()}>
                    Find readers
                </Button>
                {readers.length > 0 ? (
                    <NativeSelect
                        value={selectedReaderId}
                        onChange={(event) => setSelectedReaderId(event.target.value)}
                    >
                        {readers.map((reader) => (
                            <option key={reader.id} value={reader.id}>
                                {reader.label}
                            </option>
                        ))}
                    </NativeSelect>
                ) : null}
                <Button
                    type="button"
                    variant="outline"
                    disabled={!selectedReaderId}
                    onClick={() => void connectReader()}
                >
                    Connect reader
                </Button>
            </div>

            {status ? <p className="text-sm text-muted-foreground">{status}</p> : null}
            {error ? (
                <Alert variant="destructive">
                    <AlertDescription>{error}</AlertDescription>
                </Alert>
            ) : null}

            <Button
                type="button"
                disabled={!connected || processing}
                onClick={() => void collectPayment()}
            >
                {processing ? 'Processing…' : `Charge ${amountLabel} on reader`}
            </Button>
        </div>
    );
}
