import { useCallback, useEffect, useRef, useState, type FormEvent } from 'react'
import { Link, useNavigate, useParams, useSearchParams } from 'react-router-dom'
import { AlertCircle, ArrowRight, CheckCircle2, Eye, EyeOff, Hash, Link2, Lock, Mail, User, Video } from 'lucide-react'
import {
  guestJoinConference,
  joinConference,
  resolveConference,
  type ConferenceJoinData,
} from '@/api/conferences'
import { ApiError, errorDetail } from '@/api/client'
import { useAuth } from '@/auth/useAuth'
import { AppFooter } from '@/components/layout/AppFooter'
import { DeviceCheckCard } from '@/components/auth/DeviceCheckCard'
import { LogoMark } from '@/components/ui/LogoMark'
import { ThemeToggle } from '@/components/ui/ThemeToggle'
import { useDeviceCheckAccess } from '@/hooks/useDeviceCheckAccess'
import { useMyBackgrounds } from '@/hooks/useMyBackgrounds'
import { BackgroundPicker } from '@/components/background/BackgroundPicker'
import { isDesktopDevice } from '@/lib/isDesktopDevice'
import {
  isVirtualBackgroundSupported,
  loadBackgroundKey,
  resolveBackgroundUrl,
  saveBackgroundKey,
  type BackgroundKey,
} from '@/lib/virtualBackground'
import { usePublicSettings } from '@/hooks/usePublicSettings'
import { parseJoinQuery } from '@/lib/parseJoinQuery'
import '@/styles/join.css'

type Step = 'input' | 'guest-info' | 'password' | 'connecting' | 'ended'

interface ResolvedTarget {
  id: string
  title: string | null
  isClosed: boolean
  requiresPassword: boolean
  /** Нормализованный запрос пользователя (slug/номер) — используется как параметр `/room/:slug`. */
  query: string
}

/**
 * Подключение к конференции (см. design/mockups/join.html) — три состояния
 * одного флоу: (а) ввод ссылки/номера, (б) «представиться» для гостя,
 * (в) пароль закрытой конференции. Экран публичный — доступен без сессии
 * (гость входит без аккаунта), поэтому не завёрнут в `RequireAuth`.
 *
 * Маршруты: `/join` (ручной ввод, опционально `?q=` — например, после
 * редиректа из RoomPage при потере join-состояния) и `/j/:slug` (прямая
 * ссылка-приглашение — резолвится автоматически при заходе).
 *
 * Авторизованный пользователь пропускает «представиться»: после резолва либо
 * сразу входит (если конференция открыта), либо сразу видит экран пароля
 * (если закрыта). Гость всегда проходит «представиться»; если конференция
 * закрыта, экран пароля показывается вторым шагом, после чего оба поля
 * (имя/email + пароль) уходят одним запросом `guest-join`.
 */
export function JoinPage() {
  const navigate = useNavigate()
  const { slug: slugParam } = useParams<{ slug?: string }>()
  const [searchParams] = useSearchParams()
  const { status } = useAuth()
  const isAuthenticated = status === 'authenticated'

  const initialQuery = slugParam ?? searchParams.get('q') ?? ''
  const [query, setQuery] = useState(initialQuery)
  const [resolving, setResolving] = useState(false)
  const [resolveError, setResolveError] = useState<string | null>(null)

  const [step, setStep] = useState<Step>('input')
  const [target, setTarget] = useState<ResolvedTarget | null>(null)

  const [guestName, setGuestName] = useState('')
  const [guestEmail, setGuestEmail] = useState('')

  const [password, setPassword] = useState('')
  const [showPassword, setShowPassword] = useState(false)
  const [invalidPassword, setInvalidPassword] = useState(false)
  const [joining, setJoining] = useState(false)

  const autoResolvedRef = useRef(false)

  // Проверка устройств на входе (сессия 34, правка к сессии 33) — теперь на
  // ДВУХ шагах: «Подключиться к конференции» (`input`) и «Как вас зовут?»
  // (`guest-info`), не только на гостевом. Это закрывает реальную дыру:
  // авторизованный пользователь, входящий через /join, шаг `guest-info`
  // вообще не проходит (см. `handleResolve` — сразу `connecting`), и без
  // превью на `input` проверка устройств и кнопки «войти с камерой/
  // микрофоном» ему были бы недоступны никогда. Отключаемый модуль, дефолт
  // выключен — см. `instance_settings.device_check`. Кнопки «войти с
  // камерой/микрофоном», в отличие от LoginPage, РЕАЛЬНО определяют
  // состояние входа — см. `performJoin` (решение оператора, сессия 33).
  const { data: publicSettings } = usePublicSettings()
  const deviceCheckModuleEnabled = publicSettings?.device_check_enabled ?? false
  // Флаг «мы сейчас на одном из шагов с превью» — ОБЩИЙ для обоих шагов, а
  // не сравнение с конкретным `step`, иначе переход `input → guest-info`
  // (тот же самый непрерывный проход, а не уход с превью) выглядел бы для
  // эффекта ниже как «ушли → зашли снова»: React перезапускает эффект при
  // ЛЮБОЙ смене значения в зависимостях, а `step` меняется на каждом шаге.
  // С единым булевым флагом смена `input → guest-info` не меняет флаг
  // (true → true) — эффект не перезапускается, поток не мигает и не
  // перезахватывается (что вдобавок раскрутило бы известную грабли с
  // deviceId `"default"`, см. `useDeviceCheckAccess.ts`).
  const inDeviceCheckSteps = deviceCheckModuleEnabled && (step === 'input' || step === 'guest-info')

  // Замена фона (сессия 35) прямо в превью: выбранный здесь фон и виден в
  // окошке, и переживает вход — в комнате он применится к публикуемому треку
  // (общий сохранённый выбор, `lib/virtualBackground.ts`). Условия те же, что
  // и в комнате: модуль включён в админке + ДЕСКТОПНОЕ устройство (по
  // возможностям, не по ширине окна) + браузер умеет сегментацию.
  const [backgroundCapable] = useState(
    () => isDesktopDevice() && isVirtualBackgroundSupported(),
  )
  const backgroundPickerVisible =
    backgroundCapable && (publicSettings?.virtual_background_enabled ?? false)
  const [backgroundKey, setBackgroundKey] = useState<BackgroundKey>(loadBackgroundKey)
  // Свои картинки есть только у авторизованного пользователя; гостю доступны
  // лишь дефолтные сцены (профиля у него нет, эндпоинт ответил бы 401).
  const { items: myBackgrounds } = useMyBackgrounds(
    backgroundPickerVisible && status === 'authenticated',
  )
  const backgroundUrl = backgroundPickerVisible
    ? resolveBackgroundUrl(backgroundKey, myBackgrounds)
    : null
  const handleBackgroundChange = useCallback((key: BackgroundKey) => {
    setBackgroundKey(key)
    saveBackgroundKey(key)
  }, [])

  const deviceCheck = useDeviceCheckAccess(inDeviceCheckSteps, backgroundUrl)
  // Деструктурируем то, что нужно эффекту ниже, — `deviceCheck` целиком
  // пересоздаётся на каждый рендер (не мемоизирован), а `release` сам по
  // себе стабилен (react-hooks/exhaustive-deps иначе требует весь объект
  // в зависимостях).
  const { release: releaseDeviceCheck } = deviceCheck
  // Шаги флоу — условный рендер ОДНОГО и того же компонента (не
  // размонтируют JoinPage), поэтому размонтирование хука само по себе
  // камеру не освободит: уход С ОБОИХ шагов превью (вперёд — пароль/
  // коннект, назад — в лобби) должен погасить камеру явно. Эффект с
  // cleanup по смене `inDeviceCheckSteps` (не `step`!) ловит именно уход из
  // группы шагов одним местом, а не любой шаг внутри нее.
  useEffect(() => {
    if (!inDeviceCheckSteps) return
    return () => releaseDeviceCheck()
  }, [inDeviceCheckSteps, releaseDeviceCheck])

  // Прямая ссылка (/j/:slug) или ?q= из редиректа — резолвим сразу, не
  // дожидаясь клика по «Продолжить», но только после того как известен
  // статус сессии (нужен для выбора «гость / авторизованный» флоу).
  useEffect(() => {
    if (status === 'loading' || autoResolvedRef.current || !initialQuery) return
    autoResolvedRef.current = true
    void handleResolve(initialQuery)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [status])

  async function handleResolve(rawQuery: string) {
    const normalized = parseJoinQuery(rawQuery)
    if (!normalized) return
    setResolveError(null)
    setResolving(true)
    try {
      const info = await resolveConference(normalized)
      // Завершённая конференция (ADR-001) — backend отдаёт минимальный ответ
      // без is_closed/requires_password: сразу показываем «завершена», не
      // дожидаясь шага «представиться»/пароль и попытки join (которая всё
      // равно вернёт 410).
      if (info.status === 'ended') {
        setTarget({ id: info.id, title: info.title, isClosed: false, requiresPassword: false, query: normalized })
        setStep('ended')
        return
      }
      const nextTarget: ResolvedTarget = {
        id: info.id,
        title: info.title,
        isClosed: info.is_closed ?? false,
        requiresPassword: info.requires_password ?? false,
        query: normalized,
      }
      setTarget(nextTarget)
      if (nextTarget.requiresPassword) {
        setStep('password')
      } else if (isAuthenticated) {
        setStep('connecting')
        await performJoin(nextTarget, {})
      } else {
        setStep('guest-info')
      }
    } catch (err) {
      if (err instanceof ApiError && err.status === 404) {
        setResolveError('Конференция не найдена — проверьте ссылку или номер')
      } else {
        setResolveError('Не удалось найти конференцию. Попробуйте ещё раз')
      }
      setStep('input')
    } finally {
      setResolving(false)
    }
  }

  async function performJoin(
    resolved: ResolvedTarget,
    opts: { password?: string; guestName?: string; guestEmail?: string },
  ) {
    setJoining(true)
    setInvalidPassword(false)
    try {
      let data: ConferenceJoinData
      if (isAuthenticated) {
        data = await joinConference(resolved.id, opts.password ? { password: opts.password } : undefined)
      } else {
        data = await guestJoinConference(resolved.id, {
          display_name: opts.guestName ?? guestName.trim(),
          email: (opts.guestEmail ?? guestEmail).trim() || undefined,
          password: opts.password,
        })
      }
      // Явное освобождение ДО навигации, а не расчёт на автоматическую
      // отписку хука при размонтировании: RoomPage.tsx монтируется и сам
      // запрашивает камеру/микрофон (если enterWithVideo/enterWithAudio)
      // почти сразу — если наш поток превью к этому моменту ещё не
      // остановлен, устройство может достаться LiveKit «занятым» (см.
      // требование задачи про свободную камеру к моменту входа).
      releaseDeviceCheck()
      navigate(`/room/${encodeURIComponent(resolved.query)}`, {
        replace: true,
        state: {
          livekitUrl: data.livekit_url,
          token: data.token,
          title: resolved.title,
          conferenceId: data.conference_id,
          chatEnabled: data.chat_enabled,
          handQueueEnabled: data.hand_queue_enabled,
          publishQualityCap: data.publish_quality_cap,
          stageMaxTiles: data.stage_max_tiles,
          virtualBackgroundEnabled: data.virtual_background_enabled,
          // Кнопки превью «войти с камерой/микрофоном» — теперь доступны и
          // гостю (input → guest-info → сюда), и авторизованному пользователю
          // (input → connecting → сюда, guest-info он не проходит вовсе, см.
          // докстринг выше) одним и тем же `deviceCheck`, живущим с шага
          // `input`. При модуле выключенном оба флага остаются `false`,
          // RoomPage ведёт себя как раньше.
          enterWithVideo: deviceCheck.videoEnabled,
          enterWithAudio: deviceCheck.audioEnabled,
        },
      })
    } catch (err) {
      if (err instanceof ApiError && err.status === 403 && errorDetail(err) === 'password_required') {
        setStep('password')
      } else if (err instanceof ApiError && err.status === 403 && errorDetail(err) === 'invalid_password') {
        setInvalidPassword(true)
        setStep('password')
      } else if (err instanceof ApiError && err.status === 410) {
        setStep('ended')
      } else {
        setResolveError('Не удалось подключиться к конференции. Попробуйте ещё раз позже')
        setStep('input')
      }
    } finally {
      setJoining(false)
    }
  }

  function handleInputSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()
    void handleResolve(query)
  }

  function handleGuestInfoSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()
    if (!target || !guestName.trim()) return
    if (target.requiresPassword) {
      setStep('password')
    } else {
      void performJoin(target, {})
    }
  }

  function handlePasswordSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()
    if (!target || !password) return
    void performJoin(target, { password })
  }

  function resetToInput() {
    setResolveError(null)
    setInvalidPassword(false)
    setStep('input')
  }

  if (step === 'connecting') {
    return (
      <div className="app-loader" role="status" aria-live="polite">
        Подключаемся к конференции…
      </div>
    )
  }

  return (
    <div className="join-shell">
      <header className="join-topbar">
        <Link to="/lobby" className="brand-mark">
          <LogoMark /> VidConf
        </Link>
        <ThemeToggle />
      </header>

      <main className="join-main">
        {step === 'input' && (
          <section className="screen-block">
            <div
              className="flow-card"
              onPointerDown={deviceCheck.triggerOnGesture}
              onKeyDown={deviceCheck.triggerOnGesture}
            >
              {deviceCheckModuleEnabled ? (
                <DeviceCheckCard
                  videoRef={deviceCheck.videoRef}
                  videoStatus={deviceCheck.videoStatus}
                  audioStatus={deviceCheck.audioStatus}
                  hint={deviceCheck.hint}
                  videoEnabled={deviceCheck.videoEnabled}
                  audioEnabled={deviceCheck.audioEnabled}
                  onToggleVideo={deviceCheck.toggleVideoEnabled}
                  onToggleAudio={deviceCheck.toggleAudioEnabled}
                  backgroundPicker={
                    backgroundPickerVisible ? (
                      <>
                        <BackgroundPicker
                          value={backgroundKey}
                          onChange={handleBackgroundChange}
                          customBackgrounds={myBackgrounds}
                        />
                        {deviceCheck.backgroundFailed && (
                          <p className="bg-hint">Не удалось применить фон в превью</p>
                        )}
                      </>
                    ) : undefined
                  }
                />
              ) : (
                <div className="flow-icon" aria-hidden="true">
                  <Link2 className="icon" aria-hidden="true" />
                </div>
              )}
              <h1>Подключиться к конференции</h1>
              <p className="flow-sub">Вставьте ссылку-приглашение или введите номер конференции</p>

              <form onSubmit={handleInputSubmit}>
                <div className={`field${resolveError ? ' has-error' : ''}`}>
                  <label htmlFor="join-input">Ссылка или номер</label>
                  <div className="input-wrap">
                    <Hash className="icon" aria-hidden="true" />
                    <input
                      id="join-input"
                      type="text"
                      placeholder="vidconf.ru/j/... или 8842 104"
                      value={query}
                      onChange={(e) => setQuery(e.target.value)}
                      autoFocus
                    />
                  </div>
                  {resolveError ? (
                    <p className="error-msg">
                      <AlertCircle style={{ width: 16, height: 16 }} aria-hidden="true" /> {resolveError}
                    </p>
                  ) : (
                    <p className="field-hint">Найдите номер или ссылку в приглашении организатора</p>
                  )}
                </div>
                <button type="submit" className="btn btn-primary" disabled={!query.trim() || resolving}>
                  {resolving ? 'Ищем конференцию…' : 'Продолжить'}
                  {!resolving && <ArrowRight style={{ width: 18, height: 18 }} aria-hidden="true" />}
                </button>
              </form>
              <Link to="/lobby" className="link-back">
                Назад в лобби
              </Link>
            </div>
          </section>
        )}

        {step === 'guest-info' && target && (
          <section className="screen-block">
            <div
              className="flow-card"
              onPointerDown={deviceCheck.triggerOnGesture}
              onKeyDown={deviceCheck.triggerOnGesture}
            >
              <div className="target-conf">
                <span className="icon-wrap" aria-hidden="true">
                  {target.isClosed ? <Lock className="icon" aria-hidden="true" /> : <Video className="icon" aria-hidden="true" />}
                </span>
                <div>
                  <strong>{target.title ?? 'Конференция без названия'}</strong>
                  <span>№ {target.query}</span>
                </div>
              </div>

              {deviceCheckModuleEnabled ? (
                <DeviceCheckCard
                  videoRef={deviceCheck.videoRef}
                  videoStatus={deviceCheck.videoStatus}
                  audioStatus={deviceCheck.audioStatus}
                  hint={deviceCheck.hint}
                  videoEnabled={deviceCheck.videoEnabled}
                  audioEnabled={deviceCheck.audioEnabled}
                  onToggleVideo={deviceCheck.toggleVideoEnabled}
                  onToggleAudio={deviceCheck.toggleAudioEnabled}
                  backgroundPicker={
                    backgroundPickerVisible ? (
                      <>
                        <BackgroundPicker
                          value={backgroundKey}
                          onChange={handleBackgroundChange}
                          customBackgrounds={myBackgrounds}
                        />
                        {deviceCheck.backgroundFailed && (
                          <p className="bg-hint">Не удалось применить фон в превью</p>
                        )}
                      </>
                    ) : undefined
                  }
                />
              ) : (
                <div className="flow-icon" aria-hidden="true">
                  <User className="icon" aria-hidden="true" />
                </div>
              )}
              <h1>Как вас зовут?</h1>
              <p className="flow-sub">У вас нет аккаунта VidConf — представьтесь, чтобы войти как гость</p>

              <form onSubmit={handleGuestInfoSubmit}>
                <div className="field">
                  <label htmlFor="guest-name">
                    Имя<span className="field-optional">· обязательно</span>
                  </label>
                  <div className="input-wrap">
                    <User className="icon" aria-hidden="true" />
                    <input
                      id="guest-name"
                      type="text"
                      placeholder="Как к вам обращаться"
                      value={guestName}
                      onChange={(e) => setGuestName(e.target.value)}
                      autoFocus
                    />
                  </div>
                </div>
                <div className="field">
                  <label htmlFor="guest-email">
                    Email<span className="field-optional">· необязательно</span>
                  </label>
                  <div className="input-wrap">
                    <Mail className="icon" aria-hidden="true" />
                    <input
                      id="guest-email"
                      type="email"
                      placeholder="you@mail.ru"
                      value={guestEmail}
                      onChange={(e) => setGuestEmail(e.target.value)}
                    />
                  </div>
                  <p className="field-hint">Укажите, если хотите получить саммари конференции на почту</p>
                </div>

                <button type="submit" className="btn btn-primary" disabled={!guestName.trim() || joining}>
                  {joining ? 'Входим…' : 'Войти в конференцию'}
                  {!joining && <ArrowRight style={{ width: 18, height: 18 }} aria-hidden="true" />}
                </button>
              </form>
              <button type="button" className="link-back" onClick={resetToInput}>
                Назад
              </button>
            </div>
          </section>
        )}

        {step === 'password' && target && (
          <section className="screen-block">
            <div className="flow-card">
              <div className="target-conf">
                <span className="icon-wrap" aria-hidden="true">
                  <Lock className="icon" aria-hidden="true" />
                </span>
                <div>
                  <strong>{target.title ?? 'Конференция без названия'}</strong>
                  <span>№ {target.query} · закрытая</span>
                </div>
              </div>

              <div className="flow-icon" aria-hidden="true">
                <Lock className="icon" aria-hidden="true" />
              </div>
              <h1>Введите пароль</h1>
              <p className="flow-sub">Эта конференция закрыта — доступ только по паролю от организатора</p>

              <form onSubmit={handlePasswordSubmit}>
                <div className={`field${invalidPassword ? ' has-error' : ''}`}>
                  <label htmlFor="join-pass">Пароль конференции</label>
                  <div className="input-wrap">
                    <Lock className="icon" aria-hidden="true" />
                    <input
                      id="join-pass"
                      type={showPassword ? 'text' : 'password'}
                      placeholder="••••••••"
                      value={password}
                      onChange={(e) => {
                        setPassword(e.target.value)
                        setInvalidPassword(false)
                      }}
                      autoFocus
                    />
                    <button
                      type="button"
                      className="toggle-eye"
                      aria-label={showPassword ? 'Скрыть пароль' : 'Показать пароль'}
                      onClick={() => setShowPassword((v) => !v)}
                    >
                      {showPassword ? <EyeOff className="icon" aria-hidden="true" /> : <Eye className="icon" aria-hidden="true" />}
                    </button>
                  </div>
                  {invalidPassword && (
                    <p className="error-msg">
                      <AlertCircle style={{ width: 16, height: 16 }} aria-hidden="true" /> Неверный пароль, попробуйте ещё раз
                    </p>
                  )}
                </div>
                <button type="submit" className="btn btn-primary" disabled={!password || joining}>
                  {joining ? 'Входим…' : 'Войти'}
                  {!joining && <ArrowRight style={{ width: 18, height: 18 }} aria-hidden="true" />}
                </button>
              </form>
              <button type="button" className="link-back" onClick={resetToInput}>
                Назад
              </button>
            </div>
          </section>
        )}

        {step === 'ended' && (
          <section className="screen-block">
            <div className="flow-card">
              {target && (
                <div className="target-conf">
                  <span className="icon-wrap" aria-hidden="true">
                    <CheckCircle2 className="icon" aria-hidden="true" />
                  </span>
                  <div>
                    <strong>{target.title ?? 'Конференция без названия'}</strong>
                    <span>№ {target.query} · завершена</span>
                  </div>
                </div>
              )}

              <div className="flow-icon" aria-hidden="true">
                <CheckCircle2 className="icon" aria-hidden="true" />
              </div>
              <h1>Конференция завершена</h1>
              <p className="flow-sub">Эта конференция уже завершилась и недоступна для повторного входа</p>

              <button type="button" className="btn btn-primary" onClick={resetToInput}>
                Ввести другую ссылку или номер
              </button>
              <Link to="/lobby" className="link-back">
                Назад в лобби
              </Link>
            </div>
          </section>
        )}
      </main>
      <AppFooter />
    </div>
  )
}
