'use client';

import { useCallback, useEffect, useRef, useState } from 'react';

interface ElementPosition {
    index: number;
    tag: string;
    id: string | null;
    text: string;
    visible: boolean;
    inViewport: boolean;
    viewport: { x: number; y: number; width: number; height: number };
    page: { x: number; y: number };
    screen: { x: number; y: number };
}

interface ViewportMetrics {
    scrollX: number;
    scrollY: number;
    innerWidth: number;
    innerHeight: number;
    screenX: number;
    screenY: number;
    chromeHeight: number;
    devicePixelRatio: number;
}

interface Capture {
    url: string;
    title: string;
    html: string;
    viewport: Partial<ViewportMetrics>;
    elements: ElementPosition[];
    capturedAt: number;
}

interface AutomationSummary {
    /** firm:automation — what runs are recorded against. */
    key: string;
    id: string;
    label: string;
    description: string;
    confirm: string | null;
    steps: string[];
}

interface FirmSummary {
    id: string;
    label: string;
    urlPattern: string;
    automations: AutomationSummary[];
}

interface RunLogEntry {
    at: number;
    step: string;
    ok: boolean;
    detail?: string;
}

interface Run {
    id: number;
    automationId: string;
    automationLabel: string;
    status: 'queued' | 'running' | 'done' | 'error' | 'cancelled';
    stepIndex: number;
    totalSteps: number;
    log: RunLogEntry[];
    error: string | null;
    createdAt: number;
    updatedAt: number;
}

interface RunnerStatus {
    online: boolean;
    lastSeen: number | null;
    ageMs: number | null;
    host?: string | null;
    pid?: number | null;
    dryRun?: boolean;
    busy?: boolean;
    version?: string | null;
    expectedVersion?: string;
    stale?: boolean;
}

const RUN_BADGE: Record<Run['status'], string> = {
    queued: 'bg-slate-100 text-slate-600',
    running: 'bg-blue-100 text-blue-700',
    done: 'bg-green-100 text-green-700',
    error: 'bg-red-100 text-red-700',
    cancelled: 'bg-amber-100 text-amber-700',
};

const POLL_MS = 2000;

/** Point relative URLs (stylesheets, images, fonts) at the origin the snapshot came
 *  from, otherwise the render comes out unstyled. The HTML parser hoists a leading
 *  <base> into <head>, so prepending works even if the markup has no explicit head. */
function withBaseTag(html: string, url: string): string {
    const base = `<base href="${url.replace(/"/g, '&quot;')}">`;
    const head = html.match(/<head\b[^>]*>/i);
    if (!head || head.index === undefined) return base + html;
    const at = head.index + head[0].length;
    return html.slice(0, at) + base + html.slice(at);
}

export default function AutoBuyer() {
    const [enabled, setEnabled] = useState(false);
    const [capture, setCapture] = useState<Capture | null>(null);
    const [busy, setBusy] = useState(false);
    const [error, setError] = useState<string | null>(null);
    const [view, setView] = useState<'rendered' | 'source'>('source');
    // The extension posts a new capture every few seconds, and re-feeding srcDoc would
    // reload the frame that often. So the rendered view shows a snapshot pinned when you
    // opened it — clicking Rendered again re-pins to the latest capture.
    const [pinned, setPinned] = useState<Capture | null>(null);
    const [firms, setFirms] = useState<FirmSummary[]>([]);
    const [activeFirm, setActiveFirm] = useState<string | null>(null);
    const [runs, setRuns] = useState<Run[]>([]);
    const [busyRun, setBusyRun] = useState(false);
    const [runner, setRunner] = useState<RunnerStatus | null>(null);

    // Tracks the newest capture we already hold, so the poll can skip re-downloading it.
    const lastAtRef = useRef(0);

    const poll = useCallback(async () => {
        try {
            const s = await fetch('/api/autobuyer/status', { cache: 'no-store' }).then((r) => r.json());
            setEnabled(!!s.enabled);
            if (!s.enabled) return;

            const url = lastAtRef.current
                ? `/api/autobuyer/capture?since=${lastAtRef.current}`
                : '/api/autobuyer/capture';
            const data = await fetch(url, { cache: 'no-store' }).then((r) => r.json());
            if (data.unchanged || !data.capture) return;

            lastAtRef.current = data.capture.capturedAt;
            setCapture(data.capture);
            setError(null);
        } catch (err: any) {
            setError(err?.message ?? 'Poll failed');
        }
    }, []);

    useEffect(() => {
        poll();
        const id = setInterval(poll, POLL_MS);
        return () => clearInterval(id);
    }, [poll]);

    useEffect(() => {
        fetch('/api/autobuyer/automations')
            .then((r) => r.json())
            .then((d) => {
                const list: FirmSummary[] = d.firms ?? [];
                setFirms(list);
                setActiveFirm((current) => current ?? list[0]?.id ?? null);
            })
            .catch(() => {});
    }, []);

    // Runs tick faster than the capture poll — a run is something you watch.
    const pollRuns = useCallback(async () => {
        try {
            const [d, r] = await Promise.all([
                fetch('/api/autobuyer/runs', { cache: 'no-store' }).then((x) => x.json()),
                fetch('/api/autobuyer/runner', { cache: 'no-store' }).then((x) => x.json()),
            ]);
            setRuns(d.runs ?? []);
            setRunner(r);
        } catch { /* transient; the next tick retries */ }
    }, []);

    useEffect(() => {
        pollRuns();
        const id = setInterval(pollRuns, 1000);
        return () => clearInterval(id);
    }, [pollRuns]);

    const activeRun = runs.find((r) => r.status === 'queued' || r.status === 'running') ?? null;
    const firmAutomations = firms.find((f) => f.id === activeFirm)?.automations ?? [];

    async function startRun(automation: AutomationSummary) {
        // Anything that drives the real mouse against a broker gets a confirmation.
        if (automation.confirm && !window.confirm(
            `${automation.confirm}\n\nSteps:\n${automation.steps.map((s, i) => `${i + 1}. ${s}`).join('\n')}`
        )) return;

        setBusyRun(true);
        try {
            const res = await fetch('/api/autobuyer/automations', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ automationId: automation.key }),
            });
            const data = await res.json();
            if (!res.ok) setError(data.error ?? 'Failed to start');
            else setError(null);
            await pollRuns();
        } catch (err: any) {
            setError(err?.message ?? 'Failed to start');
        } finally {
            setBusyRun(false);
        }
    }

    async function cancelRun(id: number) {
        await fetch(`/api/autobuyer/runs?id=${id}`, { method: 'DELETE' });
        await pollRuns();
    }

    async function toggle() {
        setBusy(true);
        try {
            const res = await fetch('/api/autobuyer/status', {
                method: 'PATCH',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ enabled: !enabled }),
            });
            const data = await res.json();
            setEnabled(!!data.enabled);
            setError(null);
        } catch (err: any) {
            setError(err?.message ?? 'Failed to toggle');
        } finally {
            setBusy(false);
        }
    }

    function showRendered() {
        setView('rendered');
        if (capture) setPinned(capture);
    }

    async function clearCapture() {
        await fetch('/api/autobuyer/capture', { method: 'DELETE' });
        lastAtRef.current = 0;
        setCapture(null);
        setPinned(null);
    }

    const stale = capture ? Date.now() - capture.capturedAt > 15000 : false;

    return (
        <div className="min-h-screen bg-slate-50 p-8">
            <div className="max-w-7xl mx-auto">
                <h1 className="text-2xl font-bold text-slate-900 mb-6">AutoBuyer</h1>

                {/* ── Capture switch ── */}
                <div className="bg-white border border-slate-200 rounded-xl shadow-sm mb-6">
                    <div className="flex items-center justify-between px-4 py-3">
                        <div>
                            <p className="text-sm font-medium text-slate-800">Page capture</p>
                            <p className="text-xs text-slate-400 mt-0.5">
                                While on, the AutoFirmer Capture extension posts the target tab&apos;s HTML here every few seconds
                            </p>
                        </div>
                        <button
                            onClick={toggle}
                            disabled={busy}
                            className={`text-sm px-4 py-1.5 rounded-lg font-medium transition-colors disabled:opacity-50 ${
                                enabled
                                    ? 'bg-green-500 hover:bg-green-600 text-white'
                                    : 'bg-slate-200 hover:bg-slate-300 text-slate-700'
                            }`}
                        >
                            {busy ? '…' : enabled ? 'ON' : 'OFF'}
                        </button>
                    </div>
                </div>

                {error && (
                    <div className="mb-6 rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm text-red-700">
                        {error}
                    </div>
                )}

                {/* ── One tab per firm ── */}
                <div className="flex items-end gap-5 mb-3 border-b border-slate-200">
                    {firms.map((firm) => (
                        <button
                            key={firm.id}
                            onClick={() => setActiveFirm(firm.id)}
                            className={`text-sm font-semibold uppercase tracking-wider pb-2 -mb-px border-b-2 transition-colors ${
                                firm.id === activeFirm
                                    ? 'text-slate-900 border-slate-900'
                                    : 'text-slate-400 border-transparent hover:text-slate-600'
                            }`}
                        >
                            {firm.label}
                        </button>
                    ))}
                    <span className="ml-auto flex items-center gap-1.5 text-xs pb-2">
                        <span className={`w-2 h-2 rounded-full ${
                            runner?.online ? (runner.busy ? 'bg-blue-500 animate-pulse' : 'bg-green-500') : 'bg-slate-300'
                        }`} />
                        <span className={runner?.online ? 'text-slate-500' : 'text-slate-400'}>
                            {!runner?.online
                                ? 'Runner offline'
                                : runner.busy ? 'Runner busy' : 'Runner online'}
                        </span>
                        {runner?.online && runner.stale && (
                    <div className="mb-3 rounded-lg border border-red-200 bg-red-50 px-4 py-2.5 text-xs text-red-800">
                        The runner is out of date (reporting v{runner.version ?? 'unknown'}, this
                        dashboard needs v{runner.expectedVersion}). It will fail on step types it
                        predates. Restart it: stop with Ctrl-C and run{' '}
                        <code className="font-mono">python clicker/runner.py</code> again.
                    </div>
                )}

                {runner?.online && runner.dryRun && (
                            <span className="ml-1 px-1.5 py-0.5 rounded bg-amber-100 text-amber-700 font-medium">
                                dry run
                            </span>
                        )}
                    </span>
                </div>

                {!runner?.online && (
                    <div className="mb-3 rounded-lg border border-slate-200 bg-slate-50 px-4 py-2.5 text-xs text-slate-600">
                        Nothing will run until the runner is started — it&apos;s the process that
                        moves the mouse. In the project directory:{' '}
                        <code className="font-mono text-slate-800">python clicker/runner.py</code>
                    </div>
                )}

                {runner?.online && runner.stale && (
                    <div className="mb-3 rounded-lg border border-red-200 bg-red-50 px-4 py-2.5 text-xs text-red-800">
                        The runner is out of date (reporting v{runner.version ?? 'unknown'}, this
                        dashboard needs v{runner.expectedVersion}). It will fail on step types it
                        predates. Restart it: stop with Ctrl-C and run{' '}
                        <code className="font-mono">python clicker/runner.py</code> again.
                    </div>
                )}

                {runner?.online && runner.dryRun && (
                    <div className="mb-3 rounded-lg border border-amber-200 bg-amber-50 px-4 py-2.5 text-xs text-amber-800">
                        The runner is in dry-run mode: it will walk through the steps and move the
                        cursor, but never press or type. Restart it without <code className="font-mono">--dry-run</code> to act for real.
                    </div>
                )}

                <div className="bg-white border border-slate-200 rounded-xl shadow-sm mb-8">
                    {firmAutomations.length === 0 ? (
                        <div className="px-4 py-6 text-center text-sm text-slate-400">
                            {firms.length === 0 ? 'No firms configured' : 'No automations for this firm yet'}
                        </div>
                    ) : firmAutomations.map((a, i) => (
                        <div
                            key={a.key}
                            className={`flex items-center justify-between px-4 py-3 ${
                                i < firmAutomations.length - 1 ? 'border-b border-slate-100' : ''
                            }`}
                        >
                            <div>
                                <p className="text-sm font-medium text-slate-800">{a.label}</p>
                                <p className="text-xs text-slate-400 mt-0.5">
                                    {a.description} · {a.steps.length} step{a.steps.length === 1 ? '' : 's'}
                                </p>
                            </div>
                            <button
                                onClick={() => startRun(a)}
                                disabled={!enabled || busyRun || activeRun !== null || !runner?.online || !!runner?.stale}
                                title={
                                    !enabled ? 'AutoBuyer is switched off'
                                        : !runner?.online ? 'The runner is not running'
                                            : runner?.stale ? 'The runner is out of date — restart it'
                                            : activeRun !== null ? 'A run is already in progress'
                                                : undefined
                                }
                                className="text-sm px-4 py-1.5 rounded-lg font-medium bg-blue-500 hover:bg-blue-600 text-white transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
                            >
                                {a.label}
                            </button>
                        </div>
                    ))}
                </div>

                {/* ── Run status ── */}
                {runs.length > 0 && (
                    <>
                        <h2 className="text-sm font-semibold uppercase tracking-wider text-slate-500 mb-3">
                            Runs
                        </h2>
                        <div className="bg-white border border-slate-200 rounded-xl shadow-sm mb-8 divide-y divide-slate-100">
                            {runs.map((run) => (
                                <div key={run.id} className="px-4 py-3">
                                    <div className="flex items-center gap-3">
                                        <span className={`text-xs font-medium px-2 py-0.5 rounded ${RUN_BADGE[run.status]}`}>
                                            {run.status}
                                        </span>
                                        <span className="text-sm text-slate-800">{run.automationLabel}</span>
                                        <span className="text-xs font-mono text-slate-400">
                                            step {Math.min(run.stepIndex + (run.status === 'done' ? 0 : 1), run.totalSteps)}/{run.totalSteps}
                                        </span>
                                        <span className="ml-auto text-xs text-slate-400">
                                            {new Date(run.createdAt).toLocaleTimeString()}
                                        </span>
                                        {(run.status === 'queued' || run.status === 'running') && (
                                            <button
                                                onClick={() => cancelRun(run.id)}
                                                className="text-xs text-slate-400 hover:text-red-500 transition-colors"
                                            >
                                                Stop
                                            </button>
                                        )}
                                    </div>
                                    {run.log.length > 0 && (
                                        <ol className="mt-2 space-y-0.5">
                                            {run.log.map((entry, i) => (
                                                <li key={i} className="text-xs font-mono text-slate-500">
                                                    <span className={entry.ok ? 'text-green-600' : 'text-red-500'}>
                                                        {entry.ok ? '✓' : '✗'}
                                                    </span>{' '}
                                                    {entry.step}
                                                    {entry.detail && <span className="text-slate-400"> — {entry.detail}</span>}
                                                </li>
                                            ))}
                                        </ol>
                                    )}
                                    {run.error && (
                                        <p className="mt-1 text-xs text-red-600">{run.error}</p>
                                    )}
                                </div>
                            ))}
                        </div>
                    </>
                )}

                {/* ── Latest capture ── */}
                <h2 className="text-sm font-semibold uppercase tracking-wider text-slate-500 mb-3">
                    Latest capture
                </h2>

                {!enabled ? (
                    <div className="bg-white border border-slate-200 rounded-xl shadow-sm p-8 text-center text-slate-400">
                        Capture is off
                    </div>
                ) : !capture ? (
                    <div className="bg-white border border-slate-200 rounded-xl shadow-sm p-8 text-center text-slate-400">
                        Waiting for the extension… open a page in Chrome with the extension installed
                    </div>
                ) : (
                    <div className="bg-white border border-slate-200 rounded-xl shadow-sm overflow-hidden">
                        <div className="flex flex-wrap items-baseline gap-x-4 gap-y-1 px-4 py-3 border-b border-slate-100">
                            <span className="text-sm font-medium text-slate-800">{capture.title || '(untitled)'}</span>
                            <span className="text-xs font-mono text-slate-400 break-all">{capture.url}</span>
                            <span className="ml-auto flex items-center gap-3 text-xs text-slate-400">
                                <span>{(capture.html.length / 1024).toFixed(1)} KB</span>
                                <span className={stale ? 'text-amber-600' : ''}>
                                    {new Date(capture.capturedAt).toLocaleTimeString()}
                                    {stale && ' (stale)'}
                                </span>
                                <span className="flex rounded-md border border-slate-200 overflow-hidden">
                                    {(['rendered', 'source'] as const).map((v) => (
                                        <button
                                            key={v}
                                            onClick={() => (v === 'rendered' ? showRendered() : setView('source'))}
                                            className={`px-2 py-0.5 capitalize transition-colors ${
                                                view === v
                                                    ? 'bg-slate-700 text-white'
                                                    : 'text-slate-500 hover:bg-slate-50'
                                            }`}
                                        >
                                            {v}
                                        </button>
                                    ))}
                                </span>
                                <button
                                    onClick={clearCapture}
                                    className="text-slate-400 hover:text-red-500 transition-colors"
                                >
                                    Clear
                                </button>
                            </span>
                        </div>

                        {capture.viewport?.innerWidth != null && (
                            <div className="px-4 py-2 border-b border-slate-100 flex flex-wrap gap-x-5 gap-y-1 text-xs font-mono text-slate-500">
                                <span>viewport {capture.viewport.innerWidth}×{capture.viewport.innerHeight}</span>
                                <span>scroll {Math.round(capture.viewport.scrollX ?? 0)},{Math.round(capture.viewport.scrollY ?? 0)}</span>
                                <span>window@screen {capture.viewport.screenX},{capture.viewport.screenY}</span>
                                <span>chrome {capture.viewport.chromeHeight}px</span>
                                <span>dpr {capture.viewport.devicePixelRatio}</span>
                            </div>
                        )}

                        {capture.elements?.length > 0 && (
                            <div className="px-4 py-3 border-b border-slate-100">
                                <p className="text-xs uppercase tracking-wider text-slate-400 mb-2">
                                    Matched elements ({capture.elements.length})
                                </p>
                                <div className="overflow-x-auto">
                                    <table className="text-xs font-mono text-slate-600">
                                        <thead className="text-slate-400">
                                            <tr>
                                                <th className="text-left pr-4 font-normal">tag</th>
                                                <th className="text-left pr-4 font-normal">text</th>
                                                <th className="text-right pr-4 font-normal">window x,y</th>
                                                <th className="text-right pr-4 font-normal">size</th>
                                                <th className="text-right pr-4 font-normal">page x,y</th>
                                                <th className="text-right pr-4 font-normal">screen x,y</th>
                                                <th className="text-left font-normal">vis</th>
                                            </tr>
                                        </thead>
                                        <tbody>
                                            {capture.elements.map((el) => (
                                                <tr key={el.index} className="border-t border-slate-50">
                                                    <td className="pr-4 py-1">{el.tag}{el.id ? `#${el.id}` : ''}</td>
                                                    <td className="pr-4 py-1 max-w-xs truncate">{el.text}</td>
                                                    <td className="pr-4 py-1 text-right">{Math.round(el.viewport.x)},{Math.round(el.viewport.y)}</td>
                                                    <td className="pr-4 py-1 text-right">{Math.round(el.viewport.width)}×{Math.round(el.viewport.height)}</td>
                                                    <td className="pr-4 py-1 text-right">{Math.round(el.page.x)},{Math.round(el.page.y)}</td>
                                                    <td className="pr-4 py-1 text-right">{Math.round(el.screen.x)},{Math.round(el.screen.y)}</td>
                                                    <td className="py-1">{el.inViewport ? '✓' : el.visible ? 'off-screen' : 'hidden'}</td>
                                                </tr>
                                            ))}
                                        </tbody>
                                    </table>
                                </div>
                            </div>
                        )}

                        {view === 'rendered' ? (
                            <>
                                {pinned && pinned.capturedAt !== capture.capturedAt && (
                                    <div className="px-4 py-1.5 border-b border-slate-100 text-xs text-amber-600">
                                        Frozen snapshot from {new Date(pinned.capturedAt).toLocaleTimeString()} — click Rendered again to refresh
                                    </div>
                                )}
                                {/* allow-scripts WITHOUT allow-same-origin: the frame runs the
                                    page's own JS inside an opaque origin, so it cannot reach this
                                    dashboard's DOM, storage, or same-origin API routes — /api/firms
                                    serves firm credentials. Granting both flags together is what
                                    would let a frame drop its own sandbox.
                                    Scripts are needed because most sites ship content at opacity:0
                                    and fade it in with JS — blocked, they render blank. */}
                                <iframe
                                    sandbox="allow-scripts"
                                    srcDoc={withBaseTag((pinned ?? capture).html, (pinned ?? capture).url)}
                                    title="Captured page"
                                    className="block w-full h-[70vh] bg-white"
                                />
                            </>
                        ) : (
                            <pre className="max-h-[70vh] overflow-auto bg-slate-900 text-slate-200 text-xs font-mono p-4 whitespace-pre-wrap break-all">
                                {capture.html}
                            </pre>
                        )}
                    </div>
                )}
            </div>
        </div>
    );
}
