import { useCallback, useEffect, useRef, useState } from "react";
import { Button } from "~/shared/components/ui/button";
import { Input } from "~/shared/components/ui/input";
import { Select } from "~/shared/components/ui/select";
import { TimeInput } from "~/shared/components/ui/time-input";
import { api } from "~/shared/lib/api";
import { LANG_NAMES } from "~/shared/lib/lang";
import { MqttSection } from "./MqttSection";

interface ScheduleWindow {
	enabled: boolean;
	start: string;
	end: string;
}

interface ScheduleConfig {
	job_sleep_seconds: number;
	scan: ScheduleWindow;
	process: ScheduleWindow;
}

interface SettingsData {
	config: Record<string, string>;
	envLocked: string[];
}

/** Server response from /api/settings/{jellyfin,radarr,sonarr}. */
interface SaveResult {
	ok: boolean; // connection test passed
	saved?: boolean; // values were persisted (true even when test failed)
	testError?: string;
	error?: string; // validation error (URL/API key required)
}

let settingsCache: SettingsData | null = null;

const LANGUAGE_OPTIONS = Object.entries(LANG_NAMES).map(([code, label]) => ({ code, label }));

// ─── Locked input ─────────────────────────────────────────────────────────────

function LockedInput({ locked, ...props }: { locked: boolean } & React.InputHTMLAttributes<HTMLInputElement>) {
	return (
		<div className="relative">
			<Input {...props} disabled={locked || props.disabled} className={locked ? "pr-9" : ""} />
			{locked && (
				<span
					className="absolute right-2.5 top-1/2 -translate-y-1/2 text-[0.9rem] opacity-40 pointer-events-none select-none"
					title="Set via environment variable — edit your .env file to change this value"
				>
					🔒
				</span>
			)}
		</div>
	);
}

// Note: the section-header EnvBadge was removed — the per-input lock icon
// (LockedInput) already signals when a value is env-controlled, the badge
// was duplicate noise.

// ─── Secret input (password-masked with eye-icon reveal) ──────────────────────

function EyeIcon({ open }: { open: boolean }) {
	// GNOME-style eye / crossed-eye glyphs as inline SVG so they inherit
	// currentColor instead of fighting emoji rendering across OSes.
	if (open) {
		return (
			<svg
				xmlns="http://www.w3.org/2000/svg"
				width="16"
				height="16"
				viewBox="0 0 24 24"
				fill="none"
				stroke="currentColor"
				strokeWidth="2"
				strokeLinecap="round"
				strokeLinejoin="round"
				aria-hidden="true"
			>
				<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94" />
				<path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19" />
				<path d="M14.12 14.12a3 3 0 1 1-4.24-4.24" />
				<line x1="1" y1="1" x2="23" y2="23" />
			</svg>
		);
	}
	return (
		<svg
			xmlns="http://www.w3.org/2000/svg"
			width="16"
			height="16"
			viewBox="0 0 24 24"
			fill="none"
			stroke="currentColor"
			strokeWidth="2"
			strokeLinecap="round"
			strokeLinejoin="round"
			aria-hidden="true"
		>
			<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
			<circle cx="12" cy="12" r="3" />
		</svg>
	);
}

/**
 * Input for API keys / passwords. Shows "***" masked when the server returns
 * a secret value (the raw key never reaches this component by default). Eye
 * icon fetches the real value via /api/settings/reveal and shows it. Users
 * can also type a new value directly — any edit clears the masked state.
 */
function SecretInput({
	configKey,
	locked,
	value,
	onChange,
	placeholder,
	className,
}: {
	configKey: string;
	locked: boolean;
	value: string;
	onChange: (next: string) => void;
	placeholder?: string;
	className?: string;
}) {
	const [revealed, setRevealed] = useState(false);
	const isMasked = value === "***";

	const toggle = async () => {
		if (revealed) {
			setRevealed(false);
			return;
		}
		if (isMasked) {
			try {
				const res = await api.get<{ value: string }>(`/api/settings/reveal?key=${encodeURIComponent(configKey)}`);
				onChange(res.value);
			} catch {
				/* ignore — keep masked */
			}
		}
		setRevealed(true);
	};

	return (
		<div className={`relative ${className ?? ""}`}>
			<Input
				type={revealed || !isMasked ? "text" : "password"}
				value={value}
				disabled={locked}
				onChange={(e) => onChange(e.target.value)}
				placeholder={placeholder}
				className="pr-9"
			/>
			{locked ? (
				<span
					className="absolute inset-y-0 right-0 flex items-center pr-2.5 text-[0.9rem] opacity-40 pointer-events-none select-none"
					title="Set via environment variable — edit your .env file to change this value"
				>
					🔒
				</span>
			) : (
				<button
					type="button"
					onClick={toggle}
					tabIndex={-1}
					className="absolute inset-y-0 right-0 flex items-center px-2.5 text-gray-400 hover:text-gray-700 focus:outline-none focus-visible:text-gray-700"
					title={revealed ? "Hide" : "Reveal"}
					aria-label={revealed ? "Hide secret" : "Reveal secret"}
				>
					<EyeIcon open={revealed} />
				</button>
			)}
		</div>
	);
}

// ─── Section card ──────────────────────────────────────────────────────────────

function SectionCard({
	title,
	subtitle,
	children,
}: {
	title: React.ReactNode;
	subtitle?: React.ReactNode;
	children: React.ReactNode;
}) {
	return (
		<div className="border border-gray-200 rounded-lg p-4 mb-4">
			<div className="font-semibold text-sm mb-1">{title}</div>
			{subtitle && <p className="text-gray-500 text-sm mb-3 mt-0">{subtitle}</p>}
			{children}
		</div>
	);
}

// ─── Sortable language list ─────────────────────────────────────────────────────

function SortableLanguageList({
	langs,
	onChange,
	disabled,
}: {
	langs: string[];
	onChange: (langs: string[]) => void;
	disabled: boolean;
}) {
	const available = LANGUAGE_OPTIONS.filter((o) => !langs.includes(o.code));

	const move = (idx: number, dir: -1 | 1) => {
		const next = [...langs];
		const target = idx + dir;
		if (target < 0 || target >= next.length) return;
		[next[idx], next[target]] = [next[target], next[idx]];
		onChange(next);
	};

	const remove = (idx: number) => onChange(langs.filter((_, i) => i !== idx));

	const add = (code: string) => {
		if (code && !langs.includes(code)) onChange([...langs, code]);
	};

	return (
		<div>
			{langs.length > 0 && (
				<div className="space-y-1 mb-2">
					{langs.map((code, i) => {
						const label = LANG_NAMES[code] ?? code.toUpperCase();
						return (
							<div key={code} className="flex items-center gap-1.5 text-sm">
								<button
									type="button"
									disabled={disabled || i === 0}
									onClick={() => move(i, -1)}
									aria-label={`Move ${label} up`}
									className="w-6 h-6 flex items-center justify-center rounded border border-gray-200 bg-white text-gray-500 hover:bg-gray-50 disabled:opacity-30 disabled:cursor-not-allowed cursor-pointer text-xs"
								>
									↑
								</button>
								<button
									type="button"
									disabled={disabled || i === langs.length - 1}
									onClick={() => move(i, 1)}
									aria-label={`Move ${label} down`}
									className="w-6 h-6 flex items-center justify-center rounded border border-gray-200 bg-white text-gray-500 hover:bg-gray-50 disabled:opacity-30 disabled:cursor-not-allowed cursor-pointer text-xs"
								>
									↓
								</button>
								<span className="min-w-[8rem]">
									{label} <span className="text-gray-400 text-xs font-mono">({code})</span>
								</span>
								<button
									type="button"
									disabled={disabled}
									onClick={() => remove(i)}
									aria-label={`Remove ${label}`}
									className="text-red-400 hover:text-red-600 text-sm border-0 bg-transparent cursor-pointer disabled:opacity-30 disabled:cursor-not-allowed"
								>
									✕
								</button>
							</div>
						);
					})}
				</div>
			)}
			{!disabled && available.length > 0 && (
				<Select
					value=""
					onChange={(e) => {
						add(e.target.value);
						e.target.value = "";
					}}
					className="text-sm max-w-[14rem]"
				>
					<option value="">+ Add language…</option>
					{available.map(({ code, label }) => (
						<option key={code} value={code}>
							{label} ({code})
						</option>
					))}
				</Select>
			)}
		</div>
	);
}

// ─── Connection section ────────────────────────────────────────────────────────

function ConnSection({
	title,
	subtitle,
	cfg,
	locked,
	urlKey,
	apiKey: apiKeyProp,
	urlPlaceholder,
	onSave,
	children,
}: {
	title: React.ReactNode;
	subtitle?: React.ReactNode;
	cfg: Record<string, string>;
	locked: Set<string>;
	urlKey: string;
	apiKey: string;
	urlPlaceholder: string;
	onSave: (url: string, apiKey: string) => Promise<SaveResult>;
	children?: React.ReactNode;
}) {
	const [url, setUrl] = useState(cfg[urlKey] ?? "");
	const [key, setKey] = useState(cfg[apiKeyProp] ?? "");
	const [status, setStatus] = useState<
		| { kind: "saved" }
		| { kind: "saved-test-failed"; error: string }
		| { kind: "validation-error"; error: string }
		| { kind: "request-error"; error: string }
		| null
	>(null);
	const [saving, setSaving] = useState(false);

	const save = async () => {
		setSaving(true);
		setStatus(null);
		try {
			const result = await onSave(url, key);
			if (result.saved && result.ok) setStatus({ kind: "saved" });
			else if (result.saved) setStatus({ kind: "saved-test-failed", error: result.testError ?? "Connection test failed" });
			else setStatus({ kind: "validation-error", error: result.error ?? "Save failed" });
		} catch (e) {
			setStatus({ kind: "request-error", error: String(e) });
		}
		setSaving(false);
	};

	return (
		<SectionCard title={title} subtitle={subtitle}>
			<label className="block text-sm text-gray-700 mb-1">
				URL
				<LockedInput
					locked={locked.has(urlKey)}
					type="url"
					value={url}
					onChange={(e) => setUrl(e.target.value)}
					placeholder={urlPlaceholder}
					className="mt-0.5"
				/>
			</label>
			<label className="block text-sm text-gray-700 mb-1 mt-3">
				API Key
				<SecretInput
					configKey={apiKeyProp}
					locked={locked.has(apiKeyProp)}
					value={key}
					onChange={setKey}
					placeholder="your-api-key"
					className="mt-0.5"
				/>
			</label>
			<div className="flex items-center gap-2 mt-3">
				<Button onClick={save} disabled={saving || (locked.has(urlKey) && locked.has(apiKeyProp))}>
					{saving ? "Saving…" : "Test & Save"}
				</Button>
				{status?.kind === "saved" && <span className="text-sm text-green-700">✓ Saved &amp; connected</span>}
				{status?.kind === "saved-test-failed" && (
					<span className="text-sm text-amber-700" title={status.error}>
						⚠ Saved, but connection test failed: {status.error}
					</span>
				)}
				{status?.kind === "validation-error" && <span className="text-sm text-red-600">✗ {status.error}</span>}
				{status?.kind === "request-error" && <span className="text-sm text-red-600">✗ {status.error}</span>}
			</div>
			{children && <div className="mt-5 pt-4 border-t border-gray-200">{children}</div>}
		</SectionCard>
	);
}

// ─── Schedule section ─────────────────────────────────────────────────────────

function WindowEditor({
	label,
	window,
	onChange,
}: {
	label: string;
	window: ScheduleWindow;
	onChange: (w: ScheduleWindow) => void;
}) {
	return (
		<div className="mb-3">
			<label className="flex items-center gap-2 text-sm text-gray-700 mb-1.5">
				<input
					type="checkbox"
					checked={window.enabled}
					onChange={(e) => onChange({ ...window, enabled: e.target.checked })}
				/>
				{label}
			</label>
			{window.enabled && (
				<div className="flex items-center gap-2 pl-5">
					<TimeInput value={window.start} onChange={(next) => onChange({ ...window, start: next })} />
					<span className="text-xs text-gray-500">to</span>
					<TimeInput value={window.end} onChange={(next) => onChange({ ...window, end: next })} />
					<span className="text-xs text-gray-500">(24h, overnight ranges wrap midnight)</span>
				</div>
			)}
		</div>
	);
}

function ScheduleSection() {
	const [cfg, setCfg] = useState<ScheduleConfig | null>(null);
	const [saved, setSaved] = useState("");

	useEffect(() => {
		api.get<ScheduleConfig>("/api/settings/schedule").then(setCfg);
	}, []);

	const save = async () => {
		if (!cfg) return;
		const next = await api.patch<ScheduleConfig>("/api/settings/schedule", cfg);
		setCfg(next);
		setSaved("Saved.");
		setTimeout(() => setSaved(""), 2000);
	};

	if (!cfg) return null;

	return (
		<SectionCard
			title="Schedule"
			subtitle="Restrict when the app is allowed to scan Jellyfin and process files. Useful when this container runs as an always-on service but you only want it to do real work at night."
		>
			<WindowEditor label="Scan window" window={cfg.scan} onChange={(scan) => setCfg({ ...cfg, scan })} />
			<WindowEditor label="Processing window" window={cfg.process} onChange={(process) => setCfg({ ...cfg, process })} />
			<label className="block text-sm text-gray-700 mt-4">
				Sleep between jobs (seconds)
				<Input
					type="number"
					min={0}
					value={cfg.job_sleep_seconds}
					onChange={(e) => setCfg({ ...cfg, job_sleep_seconds: Number.parseInt(e.target.value, 10) || 0 })}
					className="mt-0.5 w-28"
				/>
			</label>
			<div className="flex items-center gap-2 mt-3">
				<Button onClick={save}>Save</Button>
				{saved && <span className="text-green-700 text-sm">{saved}</span>}
			</div>
		</SectionCard>
	);
}

// ─── Setup page ───────────────────────────────────────────────────────────────

export function SettingsPage() {
	const [data, setData] = useState<SettingsData | null>(settingsCache);
	const [loading, setLoading] = useState(settingsCache === null);
	const [clearStatus, setClearStatus] = useState("");
	const [audLangs, setAudLangs] = useState<string[]>([]);
	const [audSaved, setAudSaved] = useState("");
	const langsLoadedRef = useRef(false);

	const load = useCallback(() => {
		if (!settingsCache) setLoading(true);
		api
			.get<SettingsData>("/api/settings")
			.then((d) => {
				settingsCache = d;
				setData(d);
				if (!langsLoadedRef.current) {
					let parsed: string[] = [];
					try {
						const raw = JSON.parse(d.config.audio_languages ?? "[]");
						if (Array.isArray(raw)) parsed = raw.filter((x): x is string => typeof x === "string");
					} catch (e) {
						console.warn("audio_languages config is not valid JSON, defaulting to []", e);
					}
					setAudLangs(parsed);
					langsLoadedRef.current = true;
				}
			})
			.finally(() => setLoading(false));
	}, []);
	useEffect(() => {
		load();
	}, [load]);

	if (loading && !data) return <div className="text-gray-400 py-8 text-center">Loading…</div>;
	if (!data) return <div className="text-red-600">Failed to load settings.</div>;

	const { config: cfg, envLocked: envLockedArr } = data;
	const locked = new Set(envLockedArr);
	const saveJellyfin = (url: string, apiKey: string) =>
		api.post<SaveResult>("/api/settings/jellyfin", { url, api_key: apiKey });
	const saveRadarr = (url: string, apiKey: string) =>
		api.post<SaveResult>("/api/settings/radarr", { url, api_key: apiKey });
	const saveSonarr = (url: string, apiKey: string) =>
		api.post<SaveResult>("/api/settings/sonarr", { url, api_key: apiKey });

	const saveAudioLangs = async () => {
		await api.post("/api/settings/audio-languages", { langs: audLangs });
		setAudSaved("Saved.");
		setTimeout(() => setAudSaved(""), 2000);
	};

	const clearScan = async () => {
		if (
			!confirm(
				"Delete all scanned data? This will remove all media items, stream decisions, review plans, and jobs. This cannot be undone.",
			)
		)
			return;
		await api.post("/api/settings/clear-scan");
		setClearStatus("Cleared.");
	};

	const factoryReset = async () => {
		if (
			!confirm(
				"Reset to first-run state? This wipes EVERYTHING — scan data, settings, languages, schedule, Jellyfin/Radarr/Sonarr credentials. You'll land back on the setup wizard. This cannot be undone.",
			)
		)
			return;
		if (!confirm("Really reset? Type-nothing-just-click to confirm.")) return;
		await api.post("/api/settings/reset");
		// Invalidate client-side caches and reload so the setup gate re-evaluates.
		settingsCache = null;
		window.location.href = "/";
	};

	return (
		<div>
			<div className="flex items-center gap-3 mb-4">
				<h1 className="text-xl font-bold m-0">Settings</h1>
			</div>

			{/* Jellyfin (with nested MQTT webhook config) */}
			<ConnSection
				title="Jellyfin"
				urlKey="jellyfin_url"
				apiKey="jellyfin_api_key"
				urlPlaceholder="http://192.168.1.100:8096"
				cfg={cfg}
				locked={locked}
				onSave={saveJellyfin}
			>
				<MqttSection cfg={cfg} locked={locked} />
			</ConnSection>

			{/* Radarr */}
			<ConnSection
				title={
					<>
						Radarr <span className="text-gray-400 font-normal">(optional)</span>
					</>
				}
				subtitle="Provides accurate original-language data for movies."
				urlKey="radarr_url"
				apiKey="radarr_api_key"
				urlPlaceholder="http://192.168.1.100:7878"
				cfg={cfg}
				locked={locked}
				onSave={saveRadarr}
			/>

			{/* Sonarr */}
			<ConnSection
				title={
					<>
						Sonarr <span className="text-gray-400 font-normal">(optional)</span>
					</>
				}
				subtitle="Provides original-language data for TV series."
				urlKey="sonarr_url"
				apiKey="sonarr_api_key"
				urlPlaceholder="http://192.168.1.100:8989"
				cfg={cfg}
				locked={locked}
				onSave={saveSonarr}
			/>

			{/* Schedule */}
			<ScheduleSection />

			{/* Audio languages */}
			<SectionCard
				title="Audio Languages"
				subtitle="Additional audio languages to keep alongside the original language. Order determines stream priority in the output file. The original language is always kept first."
			>
				<SortableLanguageList langs={audLangs} onChange={setAudLangs} disabled={locked.has("audio_languages")} />
				<div className="flex items-center gap-2 mt-3">
					<Button onClick={saveAudioLangs} disabled={locked.has("audio_languages")}>
						Save
					</Button>
					{audSaved && <span className="text-green-700 text-sm">{audSaved}</span>}
				</div>
			</SectionCard>

			{/* Danger zone */}
			<div className="border border-red-400 rounded-lg p-4 mb-4">
				<div className="font-semibold text-sm text-red-700 mb-1">Danger Zone</div>
				<p className="text-gray-500 text-sm mb-3">
					These actions are irreversible. Scan data can be regenerated by running a new scan.
				</p>
				<div className="flex items-center gap-4 mb-3">
					<Button variant="danger" onClick={clearScan}>
						Clear all scan data
					</Button>
					<span className="text-gray-400 text-sm">Removes all scanned items, review plans, and jobs. Keeps settings.</span>
				</div>
				<div className="flex items-center gap-4">
					<Button variant="danger" onClick={factoryReset}>
						Reset to first-run state
					</Button>
					<span className="text-gray-400 text-sm">
						Wipes everything — scan data and settings. Sends you back to the setup wizard.
					</span>
				</div>
				{clearStatus && <p className="text-green-700 text-sm mt-2">{clearStatus}</p>}
			</div>
		</div>
	);
}

import type React from "react";
