import React, { useEffect, useRef, useState } from 'react';
import { __, sprintf, _n } from '@wordpress/i18n';
import { api } from '../api';
import { Card, Button, Badge, Notice, Tabs, Switch, ChipBar } from '../components/ui';
import DataGrid, { useServerQuery } from '../components/DataGrid';

export default function Redirects() {
	const [tab, setTab] = useState('redirects');
	const [notFound, setNotFound] = useState(null);
	const [settings, setSettings] = useState(null);
	// #/links/redirects?from=/old-path arrives from the link checker with the
	// broken path already in the form — the row's "Redirect" button used to
	// land here empty, and the path had to be retyped.
	const [form, setForm] = useState(() => {
		const q = (window.location.hash.split('?')[1] || '');
		const from = new URLSearchParams(q).get('from') || '';
		return { source: from, target: from ? '/' : '', type: 301, regex: false };
	});
	const [busy, setBusy] = useState(false);
	const [error, setError] = useState(null);
	const [csvBusy, setCsvBusy] = useState('');
	const [csvProgress, setCsvProgress] = useState('');
	const [csvReport, setCsvReport] = useState(null);
	const csvRef = useRef(null);

	// The redirects list is server-paged: with thousands of rows only the
	// current page ever reaches the browser, and search/sort run in SQL.
	const grid = useServerQuery('/redirects', { orderby: 'created_at', order: 'desc' });

	useEffect(() => {
		api.get('/404s').then(setNotFound).catch(() => {});
		api.get('/settings/redirects').then(setSettings).catch(() => {});
	}, []);

	const add = async () => {
		setBusy(true);
		setError(null);
		try {
			await api.post('/redirects', form);
			setForm({ source: '', target: '', type: 301, regex: false });
			grid.refresh();
		} catch (e) {
			setError(e.message);
		} finally {
			setBusy(false);
		}
	};

	const remove = async (id) => {
		try {
			await api.del(`/redirects/${id}`);
			grid.refresh();
		} catch (e) {
			setError(e.message);
		}
	};

	const fixNotFound = (path) => {
		setForm({ source: path, target: '/', type: 301 });
		setTab('redirects');
		window.scrollTo({ top: 0, behavior: 'smooth' });
	};

	const saveSetting = async (patch) => {
		const next = { ...settings, ...patch };
		setSettings(next);
		try {
			await api.put('/settings/redirects', next);
		} catch (e) {
			setError(e.message);
		}
	};

	/**
	 * A migration arrives as a spreadsheet. The import previews first — a
	 * three-thousand-row file that turns out to have the columns the wrong way
	 * round should say so before it writes anything.
	 */
	/*
	 * A file is applied a slice at a time.
	 *
	 * The server takes two thousand rows per request and answers with where to
	 * carry on, because fifty thousand SELECT-and-write pairs do not fit in one
	 * PHP time limit and a timeout used to leave half a migration in place with
	 * nothing on screen saying which half. The totals here are the sum of the
	 * slices, so the report reads the same as it always did.
	 */
	const importSlices = async (content, format, opts, onProgress) => {
		const total = { created: 0, updated: 0, skipped: 0, errors: [], total: 0 };
		let offset = 0;
		for (let guard = 0; guard < 200; guard += 1) {
			// eslint-disable-next-line no-await-in-loop
			const part = await api.post('/redirects/import', { content, format, ...opts, offset });
			total.created += Number(part.created || 0);
			total.updated += Number(part.updated || 0);
			total.skipped += Number(part.skipped || 0);
			total.errors = total.errors.concat(part.errors || []);
			total.total = Number(part.total || 0);
			offset = Number(part.offset || 0);
			onProgress?.(offset, total.total);
			if (!Number(part.remaining)) break;
		}
		return total;
	};

	const importCsv = async (file) => {
		setCsvBusy('import');
		setError(null);
		try {
			const content = await file.text();
			const format = /\.json$/i.test(file.name) ? 'json' : 'csv';
			const preview = await importSlices(content, format, { dry_run: true },
				(done, all) => setCsvProgress(all > 0 ? `Checking ${done.toLocaleString()} of ${all.toLocaleString()} rows…` : ''));
			setCsvProgress('');
			const question = sprintf(
				/* translators: 1: rules to add, 2: rules to update, 3: rows that were refused. */
				__('%1$s to add, %2$s to update, %3$s refused. Import them?', 'ai-rank-suite'),
				Number(preview.created).toLocaleString(),
				Number(preview.updated).toLocaleString(),
				Number((preview.errors || []).length).toLocaleString()
			);
			if (!window.confirm(question)) {
				setCsvReport({ ...preview, preview: true });
				return;
			}
			const result = await importSlices(content, format, { overwrite: preview.updated > 0 },
				(done, all) => setCsvProgress(all > 0 ? `Imported ${done.toLocaleString()} of ${all.toLocaleString()} rows…` : ''));
			setCsvProgress('');
			setCsvReport(result);
			grid.reload?.();
		} catch (e) {
			setError(e.message);
		} finally {
			setCsvBusy('');
			setCsvProgress('');
			if (csvRef.current) csvRef.current.value = '';
		}
	};

	const exportCsv = async () => {
		setCsvBusy('export');
		try {
			const { csv } = await api.get('/redirects/export');
			const a = document.createElement('a');
			a.href = URL.createObjectURL(new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8' }));
			a.download = `ai-rank-suite-redirects-${new Date().toISOString().slice(0, 10)}.csv`;
			a.click();
			URL.revokeObjectURL(a.href);
		} catch (e) {
			setError(e.message);
		} finally {
			setCsvBusy('');
		}
	};

	return (
		<div className="ars-page">
			<div className="flex items-center justify-between gap-3 flex-wrap">
				<Tabs
					tabs={[
						{ id: 'redirects', label: __('Redirects', 'ai-rank-suite') + (grid.rows ? ` (${grid.total})` : '') },
						{ id: '404', label: `404 monitor${notFound ? ` (${notFound.length})` : ''}` },
					]}
					active={tab}
					onChange={setTab}
				/>
				{settings && (
					<div className="flex items-center gap-4">
						<Button variant="ghost" onClick={exportCsv} busy={csvBusy === 'export'}>{__('Export CSV', 'ai-rank-suite')}</Button>
						<Button variant="ghost" onClick={() => csvRef.current?.click()} busy={csvBusy === 'import'}>{__('Import CSV', 'ai-rank-suite')}</Button>
						{csvProgress && <span className="t-caption">{csvProgress}</span>}
						<input
							ref={csvRef}
							type="file"
							accept=".csv,.json,text/csv,application/json"
							style={{ display: 'none' }}
							onChange={(e) => e.target.files?.[0] && importCsv(e.target.files[0])}
						/>
						<Switch checked={!!settings.monitor_404} onChange={(v) => saveSetting({ monitor_404: v })} label={__('Log 404s', 'ai-rank-suite')} />
						<Switch
							checked={!!settings.auto_redirect}
							onChange={(v) => saveSetting({ auto_redirect: v })}
							label={__('Auto-redirect on slug change', 'ai-rank-suite')}
						/>
					</div>
				)}
			</div>

			{error && <Notice tone="serious">{error}</Notice>}

			{csvReport && (
				<Notice tone={(csvReport.errors || []).length > 0 ? 'warning' : 'good'}>
					<div>
						{csvReport.preview
							? sprintf(
									/* translators: 1: rules that would be added, 2: rules that would be updated. */
									__('Nothing was written. The file holds %1$s new rules and %2$s changes.', 'ai-rank-suite'),
									Number(csvReport.created).toLocaleString(),
									Number(csvReport.updated).toLocaleString()
								)
							: sprintf(
									/* translators: 1: rules added, 2: rules updated, 3: rules left alone. */
									__('%1$s rules added, %2$s updated, %3$s left as they were.', 'ai-rank-suite'),
									Number(csvReport.created).toLocaleString(),
									Number(csvReport.updated).toLocaleString(),
									Number(csvReport.skipped).toLocaleString()
								)}
					</div>
					{(csvReport.errors || []).length > 0 && (
						<ul className="m-0 mt-1 pl-4 text-[12px]">
							{csvReport.errors.slice(0, 8).map((e, i) => (
								<li key={i}>
									{sprintf(
										/* translators: 1: row number in the file, 2: the source cell, 3: why it was refused. */
										__('Row %1$s (%2$s): %3$s', 'ai-rank-suite'),
										e.row,
										e.source || '—',
										e.reason
									)}
								</li>
							))}
						</ul>
					)}
				</Notice>
			)}

			{tab === 'redirects' && (
				<>
					<Card title={__('Add redirect', 'ai-rank-suite')} subtitle={__('Source is a path on this site; target may be a path or a full URL', 'ai-rank-suite')}>
						<div className="grid gap-3 sm:grid-cols-[1fr_1fr_120px_auto] items-end">
							<div>
								<label className="block font-medium mb-1">{form.regex ? 'Source pattern (regex)' : 'Source path'}</label>
								<input
									type="text"
									className={form.regex ? 'ars-mono' : ''}
									placeholder={form.regex ? '^/blog/(.*)$' : '/old-page'}
									value={form.source}
									onChange={(e) => setForm({ ...form, source: e.target.value })}
								/>
							</div>
							<div>
								<label className="block font-medium mb-1">{__('Target', 'ai-rank-suite')}</label>
								<input
									type="text"
									placeholder="/new-page"
									value={form.target}
									onChange={(e) => setForm({ ...form, target: e.target.value })}
								/>
							</div>
							<div>
								<label className="block font-medium mb-1">{__('Type', 'ai-rank-suite')}</label>
								<select value={form.type} onChange={(e) => setForm({ ...form, type: Number(e.target.value) })}>
									<option value={301}>301 permanent</option>
									<option value={302}>302 temporary</option>
									<option value={307}>307 temporary</option>
									<option value={410}>410 gone</option>
								</select>
							</div>
							<Button onClick={add} busy={busy} disabled={!form.source || !form.target}>{__('Add', 'ai-rank-suite')}</Button>
							<div className="sm:col-span-4">
								<Switch
									checked={!!form.regex}
									onChange={(v) => setForm({ ...form, regex: v })}
									label={__('Regular expression (use $1, $2 … in the target for captured groups)', 'ai-rank-suite')}
								/>
							</div>
						</div>
					</Card>

					<DataGrid
						query={grid}
						searchPlaceholder="Search source or target…"
						toolbar={
							<ChipBar
								label={__('Kind', 'ai-rank-suite')}
								value={grid.state.filters.status || ''}
								onChange={(v) => grid.setFilter('status', v)}
								options={[
									{ id: '', label: __('All', 'ai-rank-suite') },
									{ id: 'active', label: __('Plain', 'ai-rank-suite') },
									{ id: 'regex', label: __('Regex', 'ai-rank-suite') },
								]}
							/>
						}
						columns={[
							{
								id: 'source',
								label: __('Source', 'ai-rank-suite'),
								sortable: true,
								render: (r) => (
									<span className="ars-mono">
										{r.source}
										{r.regex && (
											<span className="ml-2">
												<Badge tone="brand">regex</Badge>
											</span>
										)}
									</span>
								),
							},
							{
								id: 'target',
								label: __('Target', 'ai-rank-suite'),
								render: (r) => (
									<span className="ars-mono ars-url" style={{ maxWidth: 300, display: 'inline-block' }} title={r.target}>
										{r.target}
									</span>
								),
							},
							{
								id: 'type',
								label: __('Type', 'ai-rank-suite'),
								render: (r) => <Badge tone={r.type === 301 ? 'brand' : 'neutral'}>{r.type}</Badge>,
							},
							{ id: 'hits', label: __('Hits', 'ai-rank-suite'), num: true, sortable: true },
						]}
						rowActions={(r) => (
							<button
								type="button"
								className="border-0 bg-transparent cursor-pointer text-[12px]"
								style={{ color: 'var(--ars-serious)' }}
								onClick={() => remove(r.id)}
							>{__('Delete', 'ai-rank-suite')}</button>
						)}
						empty={{
							title: __('No redirects yet', 'ai-rank-suite'),
							children: "Changing a published page's URL creates one automatically.",
						}}
					/>
				</>
			)}

			{tab === '404' && (
				<Card title={__('404 monitor', 'ai-rank-suite')} subtitle={__('Requests that hit nothing — each one is a lost visitor or crawler', 'ai-rank-suite')}>
					{!notFound || notFound.length === 0 ? (
						<div className="grid gap-2">
							<p className="m-0 text-[13px]" style={{ color: 'var(--ars-ink-2)' }}>{__('No 404s logged yet.', 'ai-rank-suite')}</p>
							<p className="m-0 t-caption">
								{__('From now on, every request to a path that does not exist on this site is recorded here with how often it was hit and when it was last seen: a visitor following a stale link, a crawler still asking for a page you deleted, a typo in somebody else\'s link to you. Each one is a lost visit until it has a redirect, and the Redirects tab is where it gets one. Old entries are pruned after the number of days set under Settings, Data.', 'ai-rank-suite')}
							</p>
						</div>
					) : (
						<div className="ars-table-wrap"><table className="ars-table">
							<thead>
								<tr>
									<th>{__('Path', 'ai-rank-suite')}</th>
									<th className="is-num">{__('Hits', 'ai-rank-suite')}</th>
									<th>{__('Last seen', 'ai-rank-suite')}</th>
									<th />
								</tr>
							</thead>
							<tbody>
								{notFound.map((n) => (
									<tr key={n.id}>
										<td className="ars-mono" style={{ maxWidth: 340 }} title={n.path}>
											<span className="ars-url">{n.path}</span>
										</td>
										<td className="is-num">{n.hits}</td>
										<td style={{ color: 'var(--ars-ink-3)' }}>{n.last_hit}</td>
										<td className="is-num">
											<Button variant="ghost" onClick={() => fixNotFound(n.path)}>{__('Redirect', 'ai-rank-suite')}</Button>
										</td>
									</tr>
								))}
							</tbody>
						</table></div>
					)}
				</Card>
			)}
		</div>
	);
}
