import { useCallback, useEffect, useRef, useState } from 'react';
import { Button, Card, Progress, Tag } from 'animal-island-ui';

import { Icon } from '../ui/icon.js';
import {
  BUSY_UPDATE_PHASES,
  createConsoleUpdateApiDataSource,
  type ConsoleUpdateDataSource,
  type UpdatePhase,
  type UpdateStatus,
} from './console-update-api-data-source.js';

export type {
  ConsoleUpdateDataSource,
  UpdatePhase,
  UpdateStatus,
} from './console-update-api-data-source.js';

const FAST_POLL_MS = 1_000;
const SLOW_POLL_MS = 30_000;

const PHASE_LABELS: Readonly<Record<UpdatePhase, string>> = {
  idle: '待检查',
  checking: '正在检查',
  up_to_date: '已是最新',
  update_available: '发现新版本',
  downloading: '正在下载',
  ready: '待安装',
  installing: '正在安装',
  install_queued: '安装已排队',
  restarting: '正在重启',
  failed: '更新失败',
  rolled_back: '已回滚',
};

function formatTime(value: string): string {
  if (!value) return '尚未检查';
  return new Intl.DateTimeFormat('zh-CN', { dateStyle: 'medium', timeStyle: 'short' }).format(
    new Date(value),
  );
}

function shortHash(commit: string): string {
  return commit ? commit.slice(0, 8) : '--';
}

/**
 * Online update for the console itself, in the same four steps the Hub uses:
 * check, download, install, restart. The panel keeps polling so a restart that
 * swaps the process underneath it lands on the confirmed version on its own.
 */
export function ConsoleUpdatePanel({
  dataSource,
  onCompleted,
}: {
  readonly dataSource?: ConsoleUpdateDataSource | undefined;
  readonly onCompleted?: ((message: string) => void) | undefined;
}) {
  const [source] = useState(() => dataSource ?? createConsoleUpdateApiDataSource());
  const [status, setStatus] = useState<UpdateStatus>();
  const [loading, setLoading] = useState(true);
  const [failed, setFailed] = useState(false);
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState('');
  const [attempt, setAttempt] = useState(0);
  const timer = useRef<number | undefined>(undefined);
  const phaseRef = useRef<UpdatePhase>('idle');

  const apply = useCallback((next: UpdateStatus) => {
    phaseRef.current = next.phase;
    setStatus(next);
    setLoading(false);
    setFailed(false);
  }, []);

  // Silent refresh used by the poll loop: a transient failure keeps the last known state.
  const refresh = useCallback(async () => {
    try {
      apply(await source.status());
    } catch {
      /* the explicit reload path reports the problem */
    }
  }, [apply, source]);

  useEffect(() => {
    const controller = new AbortController();
    let active = true;
    setLoading(true);
    setFailed(false);
    source.status(controller.signal).then(
      (next) => {
        if (!active) return;
        apply(next);
      },
      () => {
        if (!active || controller.signal.aborted) return;
        setFailed(true);
        setLoading(false);
      },
    );
    return () => {
      active = false;
      controller.abort();
    };
  }, [apply, attempt, source]);

  // Poll fast while a phase is in flight, otherwise stay quiet but self-healing. The phase
  // lives in a ref so a status change never restarts the timer mid-countdown.
  const pollingEnabled = !loading && !failed;
  useEffect(() => {
    if (!pollingEnabled) return;
    let cancelled = false;
    const schedule = () => {
      const delay = BUSY_UPDATE_PHASES.has(phaseRef.current) ? FAST_POLL_MS : SLOW_POLL_MS;
      timer.current = window.setTimeout(() => {
        void refresh().finally(() => {
          if (!cancelled) schedule();
        });
      }, delay);
    };
    schedule();
    return () => {
      cancelled = true;
      window.clearTimeout(timer.current);
    };
  }, [pollingEnabled, refresh]);

  const run = async (action: () => Promise<UpdateStatus>, confirmText?: string) => {
    if (busy) return;
    if (confirmText && !window.confirm(confirmText)) return;
    setBusy(true);
    setError('');
    try {
      const next = await action();
      apply(next);
      if (next.message) onCompleted?.(next.message);
    } catch (caught) {
      setError(caught instanceof Error ? caught.message : '更新操作未完成，请稍后重试。');
      try {
        apply(await source.status());
      } catch {
        /* keep the reported error on screen */
      }
    } finally {
      setBusy(false);
    }
  };

  const phase = status?.phase ?? 'idle';
  const release = status?.release ?? null;
  const busyPhase = BUSY_UPDATE_PHASES.has(phase);
  const blocked = busy || busyPhase;
  const canDownload = phase === 'update_available' || phase === 'failed' || phase === 'rolled_back';
  const canInstall = phase === 'ready';
  const canRestart = Boolean(status?.restartSupported) && phase !== 'restarting';

  return (
    <Card pattern="default" className="settings-card maintenance-card update-card">
      <section aria-label="在线更新">
        <h2>
          <Icon name="version" />
          在线更新
        </h2>
        <p className="maintenance-hint">
          检查、下载、安装并重启控制台，全过程分步可见，失败自动回滚代码。
        </p>

        {loading ? (
          <p role="status" aria-label="在线更新加载状态">
            正在读取更新状态…
          </p>
        ) : null}
        {failed ? (
          <div role="alert">
            <p>无法加载更新状态。</p>
            <Button
              htmlType="button"
              onClick={() => {
                setAttempt((value) => value + 1);
              }}
            >
              重试加载
            </Button>
          </div>
        ) : null}

        {!loading && !failed && status ? (
          <>
            <div className="update-status-row">
              <Tag
                size="small"
                variant="soft"
                color={
                  busyPhase
                    ? 'app-orange'
                    : phase === 'failed' || phase === 'rolled_back'
                      ? 'app-red'
                      : 'app-green'
                }
              >
                {PHASE_LABELS[phase]}
              </Tag>
              <span className="update-phase-message">{status.message || '等待检查更新。'}</span>
            </div>

            {busyPhase ? (
              <div className="update-progress">
                <Progress
                  percent={status.progressPercent}
                  showInfo={false}
                  duration={0}
                  aria-label="更新进度"
                />
                <strong>{status.progressPercent}%</strong>
              </div>
            ) : null}

            <dl className="maintenance-summary update-summary">
              <div>
                <dt>部署方式</dt>
                <dd>{status.deploymentMode === 'git' ? 'Git 仓库' : '手动部署'}</dd>
              </div>
              <div>
                <dt>当前版本</dt>
                <dd>
                  {release
                    ? `${release.currentVersion || '--'} (${shortHash(release.currentCommit)})`
                    : '--'}
                </dd>
              </div>
              <div>
                <dt>最新版本</dt>
                <dd>
                  {release && release.status !== 'unavailable'
                    ? `${release.latestVersion || '--'} (${shortHash(release.latestCommit)})`
                    : '--'}
                </dd>
              </div>
              <div>
                <dt>更新来源</dt>
                <dd>{release ? `${release.remote} / ${release.branch}` : '--'}</dd>
              </div>
              <div>
                <dt>落后提交</dt>
                <dd>{release ? `${release.ahead} 个` : '--'}</dd>
              </div>
              <div>
                <dt>变更规模</dt>
                <dd>
                  {release && release.changedFiles > 0
                    ? `${release.changedFiles} 文件 / ${release.changedLines} 行`
                    : '--'}
                </dd>
              </div>
              <div>
                <dt>最近检查</dt>
                <dd>{formatTime(status.checkedAt)}</dd>
              </div>
              <div>
                <dt>可安装</dt>
                <dd>{status.installSupported ? '支持' : '不支持'}</dd>
              </div>
            </dl>

            {release?.summary ? <p className="update-release-summary">{release.summary}</p> : null}

            {status.error ? (
              <p role="alert" className="maintenance-error">
                {status.error}
              </p>
            ) : null}

            <div className="update-actions">
              <Button
                htmlType="button"
                loading={busy && phase === 'checking'}
                disabled={blocked}
                onClick={() => void run(() => source.check())}
              >
                检查更新
              </Button>
              <Button
                htmlType="button"
                type="primary"
                loading={busy && phase === 'downloading'}
                disabled={blocked || !canDownload}
                onClick={() => void run(() => source.download())}
              >
                下载并准备更新
              </Button>
              <Button
                htmlType="button"
                danger
                loading={busy && (phase === 'installing' || phase === 'install_queued')}
                disabled={blocked || !canInstall || !status.installSupported}
                onClick={() =>
                  void run(
                    () => source.install(),
                    '确认安装已验证的更新？控制台将更新依赖、重建前端并重启服务。',
                  )
                }
              >
                安装更新
              </Button>
              <Button
                htmlType="button"
                loading={busy && phase === 'restarting'}
                disabled={blocked || !canRestart}
                onClick={() =>
                  void run(
                    () => source.restart(),
                    '确认立即重启控制台服务？正在执行的设备任务会被中断。',
                  )
                }
              >
                <Icon name="restart" />
                重启服务
              </Button>
            </div>

            {!status.installSupported ? (
              <p className="maintenance-hint">
                当前为手动部署，无法在线安装更新，请在服务器上执行 git pull 后重启服务。
              </p>
            ) : null}

            {error ? (
              <p role="alert" className="maintenance-error">
                {error}
              </p>
            ) : null}
          </>
        ) : null}
      </section>
    </Card>
  );
}
