'use client';

import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
  AlertTriangle,
  ChevronDown,
  ChevronUp,
  CheckCircle2,
  FileArchive,
  Flame,
  History,
  Loader2,
  RefreshCw,
  RotateCcw,
  ServerCog,
  ShieldCheck,
  UploadCloud,
  XCircle,
} from 'lucide-react';
import { toast } from 'sonner';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Progress } from '@/components/ui/progress';
import { Separator } from '@/components/ui/separator';
import { cn } from '@/lib/utils';

type UpgradeMode = 'hot' | 'cold';
type UpgradeStatus =
  | 'queued'
  | 'running'
  | 'rolling_back'
  | 'succeeded'
  | 'failed'
  | 'rolled_back'
  | 'rollback_failed';

type UpgradeJob = {
  id: string;
  mode: UpgradeMode;
  status: UpgradeStatus;
  step: string;
  message: string;
  progress: number;
  packageName: string;
  packageHash?: string;
  backupFile?: string;
  sourceBackupFile?: string;
  restartRequired?: boolean;
  changedFiles?: string[];
  preExistingFiles?: string[];
  error?: string;
  startedAt: string;
  updatedAt: string;
  finishedAt?: string;
  logs: string[];
};

type UpgradeResponse = {
  latest: UpgradeJob | null;
  history: UpgradeJob[];
  running: boolean;
  stateDir: string;
};

const RUNNING_STATUSES = new Set<UpgradeStatus>(['queued', 'running', 'rolling_back']);
const FINAL_STATUSES = new Set<UpgradeStatus>(['succeeded', 'failed', 'rolled_back', 'rollback_failed']);

function getAdminAuthHeaders(): HeadersInit {
  try {
    const raw = window.localStorage.getItem('miaojing_auth');
    if (!raw) return {};
    const auth = JSON.parse(raw) as { accessToken?: string; session?: { access_token?: string } };
    const token = auth.accessToken || auth.session?.access_token;
    return token ? { Authorization: `Bearer ${token}` } : {};
  } catch {
    return {};
  }
}

export default function SystemUpgradeTab() {
  const [mode, setMode] = useState<UpgradeMode>('hot');
  const [selectedFile, setSelectedFile] = useState<File | null>(null);
  const [submitting, setSubmitting] = useState(false);
  const [loading, setLoading] = useState(true);
  const [upgradeData, setUpgradeData] = useState<UpgradeResponse>({ latest: null, history: [], running: false, stateDir: '' });
  const [currentLogJobIds, setCurrentLogJobIds] = useState<Set<string>>(new Set());
  const [expandedHistoryId, setExpandedHistoryId] = useState<string | null>(null);
  const fileInputRef = useRef<HTMLInputElement>(null);

  const latest = upgradeData.latest;
  const latestIsRunning = latest ? RUNNING_STATUSES.has(latest.status) : false;

  const loadStatus = useCallback(async ({ silent = false }: { silent?: boolean } = {}) => {
    if (!silent) setLoading(true);
    try {
      const res = await fetch('/api/admin/upgrade', {
        headers: getAdminAuthHeaders(),
        cache: 'no-store',
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(data.error || '读取升级状态失败');
      const latestJob = data.latest || null;
      if (latestJob && RUNNING_STATUSES.has(latestJob.status)) {
        setCurrentLogJobIds(previous => new Set(previous).add(latestJob.id));
      }
      setUpgradeData({
        latest: latestJob,
        history: Array.isArray(data.history) ? data.history : [],
        running: data.running === true,
        stateDir: data.stateDir || '',
      });
    } catch (error) {
      if (!silent) toast.error(error instanceof Error ? error.message : '读取升级状态失败');
    } finally {
      if (!silent) setLoading(false);
    }
  }, []);

  useEffect(() => {
    loadStatus();
  }, [loadStatus]);

  useEffect(() => {
    if (!latestIsRunning) return;
    const timer = window.setInterval(() => loadStatus({ silent: true }), 2500);
    return () => window.clearInterval(timer);
  }, [latestIsRunning, loadStatus]);

  useEffect(() => {
    if (!latest || !FINAL_STATUSES.has(latest.status)) return;
    const timer = window.setTimeout(() => loadStatus({ silent: true }), 1200);
    return () => window.clearTimeout(timer);
  }, [latest?.id, latest?.status, latest, loadStatus]);

  const canSubmit = useMemo(() => Boolean(selectedFile) && !submitting && !upgradeData.running, [selectedFile, submitting, upgradeData.running]);

  async function handleSubmit() {
    if (!selectedFile) {
      toast.error('请选择升级包');
      return;
    }
    if (!/\.(tar|tgz|tar\.gz)$/i.test(selectedFile.name)) {
      toast.error('仅支持 .tar、.tar.gz、.tgz 升级包');
      return;
    }

    setSubmitting(true);
    try {
      const form = new FormData();
      form.set('mode', mode);
      form.set('package', selectedFile);

      const res = await fetch('/api/admin/upgrade', {
        method: 'POST',
        headers: getAdminAuthHeaders(),
        body: form,
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(data.error || '创建升级任务失败');

      if (data.job?.id) {
        setCurrentLogJobIds(previous => new Set(previous).add(data.job.id));
      }
      toast.success(mode === 'hot' ? '热更新任务已启动' : '冷更新任务已启动');
      setSelectedFile(null);
      if (fileInputRef.current) fileInputRef.current.value = '';
      await loadStatus({ silent: true });
    } catch (error) {
      toast.error(error instanceof Error ? error.message : '创建升级任务失败');
    } finally {
      setSubmitting(false);
    }
  }

  return (
    <div className="space-y-6">
      <Alert className="border-amber-500/30 bg-amber-500/5">
        <ShieldCheck className="h-4 w-4 text-amber-600" />
        <AlertTitle>升级保护策略</AlertTitle>
        <AlertDescription>
          每次升级都会先创建数据库、存储、环境配置备份和源码快照；任务失败会自动回滚到升级开始前的源码与数据状态。
        </AlertDescription>
      </Alert>

      <div className="grid gap-6 xl:grid-cols-[minmax(0,1.1fr)_minmax(21rem,0.9fr)]">
        <Card>
          <CardHeader>
            <CardTitle className="flex items-center gap-2 text-lg">
              <UploadCloud className="h-5 w-5 text-primary" />
              上传升级包
            </CardTitle>
            <CardDescription>支持 tar、tar.gz、tgz 格式；热更新不重启平台，冷更新会构建并重启平台进程。</CardDescription>
          </CardHeader>
          <CardContent className="space-y-5">
            <div className="grid gap-3 sm:grid-cols-2">
              <ModeCard
                active={mode === 'hot'}
                icon={<Flame className="h-5 w-5" />}
                title="热更新"
                description="只允许 public 静态资源等不影响运行时代码的补丁，应用后不重启。"
                onClick={() => setMode('hot')}
              />
              <ModeCard
                active={mode === 'cold'}
                icon={<ServerCog className="h-5 w-5" />}
                title="冷更新"
                description="适合代码、依赖、脚本等较大变更，会校验、构建、重启并健康检查。"
                onClick={() => setMode('cold')}
              />
            </div>

            <div className="space-y-2">
              <Label htmlFor="upgrade-package">升级包</Label>
              <Input
                ref={fileInputRef}
                id="upgrade-package"
                type="file"
                accept=".tar,.tgz,.tar.gz,application/gzip,application/x-tar"
                disabled={submitting || upgradeData.running}
                onChange={event => setSelectedFile(event.target.files?.[0] || null)}
              />
              <p className="text-xs text-muted-foreground">
                热更新包如包含 src、package.json、脚本或锁文件会被拒绝，请改用冷更新。
              </p>
            </div>

            {selectedFile && (
              <div className="flex items-center justify-between gap-3 rounded-md border border-border bg-muted/35 px-3 py-2 text-sm">
                <div className="flex min-w-0 items-center gap-2">
                  <FileArchive className="h-4 w-4 shrink-0 text-primary" />
                  <span className="truncate">{selectedFile.name}</span>
                </div>
                <span className="shrink-0 text-xs text-muted-foreground">{formatBytes(selectedFile.size)}</span>
              </div>
            )}

            <div className="rounded-md border border-border bg-background/50 p-4">
              <div className="mb-2 flex items-center gap-2 text-sm font-medium">
                <AlertTriangle className="h-4 w-4 text-amber-500" />
                升级前确认
              </div>
              <ul className="ml-5 list-disc space-y-1 text-sm text-muted-foreground">
                <li>升级包不能包含 .env、node_modules、.git、backups、local-storage 等敏感目录。</li>
                <li>失败回滚会恢复源码快照、数据库备份、存储目录和环境配置。</li>
                <li>冷更新完成后会自动重启平台进程，并以 /api/health 作为成功判定。</li>
              </ul>
            </div>

            <Button onClick={handleSubmit} disabled={!canSubmit} className="gap-2">
              {submitting ? <Loader2 className="h-4 w-4 animate-spin" /> : <UploadCloud className="h-4 w-4" />}
              {submitting ? '正在上传...' : mode === 'hot' ? '启动热更新' : '启动冷更新'}
            </Button>
          </CardContent>
        </Card>

        <Card>
          <CardHeader>
            <CardTitle className="flex items-center justify-between gap-3 text-lg">
              <span className="flex items-center gap-2">
                <ServerCog className="h-5 w-5 text-primary" />
                当前状态
              </span>
              <Button variant="outline" size="sm" onClick={() => loadStatus()} disabled={loading}>
                {loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <RefreshCw className="h-4 w-4" />}
                刷新
              </Button>
            </CardTitle>
            <CardDescription>冷更新重启后也会从磁盘续上这里的任务状态。</CardDescription>
          </CardHeader>
          <CardContent>
            {loading ? (
              <div className="flex h-44 items-center justify-center text-muted-foreground">
                <Loader2 className="mr-2 h-5 w-5 animate-spin" />
                正在加载升级状态
              </div>
            ) : latest ? (
              <UpgradeStatusPanel
                job={latest}
                showLogs={latestIsRunning || currentLogJobIds.has(latest.id)}
                logTitle={latestIsRunning ? '实时升级日志' : '本次升级日志'}
              />
            ) : (
              <div className="flex h-44 flex-col items-center justify-center rounded-md border border-dashed border-border text-center">
                <History className="mb-2 h-8 w-8 text-muted-foreground" />
                <div className="text-sm font-medium">暂无升级记录</div>
                <div className="mt-1 text-xs text-muted-foreground">上传升级包后会在这里显示进度和回滚结果</div>
              </div>
            )}
          </CardContent>
        </Card>
      </div>

      <Card>
        <CardHeader>
          <CardTitle className="flex items-center gap-2 text-lg">
            <History className="h-5 w-5 text-primary" />
            升级历史
          </CardTitle>
          <CardDescription>保留全部升级任务，便于随时查看升级内容、执行日志、失败原因与回滚记录。</CardDescription>
        </CardHeader>
        <CardContent>
          {upgradeData.history.length === 0 ? (
            <div className="rounded-md border border-dashed border-border p-6 text-center text-sm text-muted-foreground">暂无历史记录</div>
          ) : (
            <div className="space-y-3">
              {upgradeData.history.map(job => (
                <div key={job.id} className="rounded-md border border-border p-3 text-sm">
                  <div className="grid gap-3 md:grid-cols-[9rem_1fr_auto] md:items-center">
                    <div className="flex items-center gap-2">
                      <StatusIcon status={job.status} />
                      <Badge variant="secondary">{job.mode === 'hot' ? '热更新' : '冷更新'}</Badge>
                    </div>
                    <div className="min-w-0">
                      <div className="truncate font-medium">{job.packageName}</div>
                      <div className="truncate text-xs text-muted-foreground">{job.message}</div>
                    </div>
                    <div className="flex items-center justify-between gap-3 md:justify-end">
                      <div className="text-xs text-muted-foreground md:text-right">{formatDate(job.updatedAt)}</div>
                      <Button
                        variant="outline"
                        size="sm"
                        onClick={() => setExpandedHistoryId(expandedHistoryId === job.id ? null : job.id)}
                      >
                        {expandedHistoryId === job.id ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
                        {expandedHistoryId === job.id ? '收起详情' : '查看详情'}
                      </Button>
                    </div>
                  </div>
                  {expandedHistoryId === job.id && (
                    <div className="mt-4">
                      <UpgradeStatusPanel job={job} showLogs logTitle="历史升级日志" />
                    </div>
                  )}
                </div>
              ))}
            </div>
          )}
        </CardContent>
      </Card>
    </div>
  );
}

function ModeCard({
  active,
  icon,
  title,
  description,
  onClick,
}: {
  active: boolean;
  icon: React.ReactNode;
  title: string;
  description: string;
  onClick: () => void;
}) {
  return (
    <button
      type="button"
      onClick={onClick}
      className={cn(
        'min-h-32 rounded-md border p-4 text-left transition-colors',
        active ? 'border-primary bg-primary/10 text-foreground' : 'border-border bg-background hover:bg-muted/50',
      )}
    >
      <div className="mb-3 flex items-center gap-2 font-semibold">
        <span className={cn('flex h-9 w-9 items-center justify-center rounded-md', active ? 'bg-primary text-zinc-950' : 'bg-muted text-muted-foreground')}>
          {icon}
        </span>
        {title}
      </div>
      <p className="text-sm leading-6 text-muted-foreground">{description}</p>
    </button>
  );
}

function UpgradeStatusPanel({
  job,
  showLogs,
  logTitle = '执行日志',
}: {
  job: UpgradeJob;
  showLogs: boolean;
  logTitle?: string;
}) {
  const changedFiles = job.changedFiles || [];
  return (
    <div className="space-y-4">
      <div className="flex flex-wrap items-center justify-between gap-3">
        <div className="flex items-center gap-2">
          <StatusIcon status={job.status} />
          <Badge variant="secondary">{job.mode === 'hot' ? '热更新' : '冷更新'}</Badge>
          <Badge className={statusBadgeClass(job.status)}>{statusLabel(job.status)}</Badge>
        </div>
        <div className="text-xs text-muted-foreground">{formatDate(job.updatedAt)}</div>
      </div>

      <div>
        <div className="mb-2 flex items-center justify-between gap-3 text-sm">
          <span className="font-medium">{job.message}</span>
          <span className="text-muted-foreground">{Math.max(0, Math.min(100, job.progress || 0))}%</span>
        </div>
        <Progress value={Math.max(0, Math.min(100, job.progress || 0))} />
      </div>

      <div className="grid gap-2 text-sm">
        <InfoRow label="任务 ID" value={job.id} />
        <InfoRow label="升级包" value={job.packageName} />
        <InfoRow label="当前步骤" value={job.step} />
        <InfoRow label="文件数量" value={`${changedFiles.length} 个文件`} />
        {job.backupFile && <InfoRow label="数据备份" value={job.backupFile} />}
        {job.sourceBackupFile && <InfoRow label="源码快照" value={job.sourceBackupFile} />}
      </div>

      {changedFiles.length > 0 && (
        <div className="rounded-md border border-border bg-muted/25 p-3">
          <div className="mb-2 text-sm font-medium">升级内容</div>
          <div className="max-h-40 overflow-auto rounded bg-background/70 p-2 font-mono text-xs leading-5 text-muted-foreground">
            {changedFiles.map(file => (
              <div key={file} className="truncate" title={file}>{file}</div>
            ))}
          </div>
        </div>
      )}

      {job.error && (
        <Alert variant={job.status === 'rollback_failed' ? 'destructive' : 'default'} className="border-amber-500/30 bg-amber-500/5">
          <RotateCcw className="h-4 w-4" />
          <AlertTitle>{job.status === 'rolled_back' ? '已自动回滚' : '升级错误'}</AlertTitle>
          <AlertDescription>{job.error}</AlertDescription>
        </Alert>
      )}

      {showLogs && job.logs?.length > 0 && (
        <>
          <Separator />
          <div>
            <div className="mb-2 flex items-center justify-between gap-3 text-sm">
              <span className="font-medium">{logTitle}</span>
              <span className="text-xs text-muted-foreground">{job.logs.length} 行</span>
            </div>
            <pre className="max-h-72 overflow-auto rounded-md bg-zinc-950 p-3 text-xs leading-5 text-zinc-100">
              {job.logs.join('\n')}
            </pre>
          </div>
        </>
      )}
    </div>
  );
}

function InfoRow({ label, value }: { label: string; value: string }) {
  return (
    <div className="grid grid-cols-[5rem_1fr] gap-3 rounded-md bg-muted/35 px-3 py-2">
      <span className="text-muted-foreground">{label}</span>
      <span className="truncate font-mono text-xs" title={value}>{value}</span>
    </div>
  );
}

function StatusIcon({ status }: { status: UpgradeStatus }) {
  if (status === 'succeeded') return <CheckCircle2 className="h-4 w-4 text-emerald-500" />;
  if (status === 'rolled_back') return <RotateCcw className="h-4 w-4 text-amber-500" />;
  if (status === 'failed' || status === 'rollback_failed') return <XCircle className="h-4 w-4 text-destructive" />;
  return <Loader2 className="h-4 w-4 animate-spin text-primary" />;
}

function statusLabel(status: UpgradeStatus): string {
  const labels: Record<UpgradeStatus, string> = {
    queued: '排队中',
    running: '执行中',
    rolling_back: '回滚中',
    succeeded: '成功',
    failed: '失败',
    rolled_back: '已回滚',
    rollback_failed: '回滚失败',
  };
  return labels[status] || status;
}

function statusBadgeClass(status: UpgradeStatus): string {
  if (status === 'succeeded') return 'bg-emerald-500/15 text-emerald-600 hover:bg-emerald-500/15';
  if (status === 'rolled_back') return 'bg-amber-500/15 text-amber-600 hover:bg-amber-500/15';
  if (status === 'failed' || status === 'rollback_failed') return 'bg-destructive/15 text-destructive hover:bg-destructive/15';
  return 'bg-primary/15 text-primary hover:bg-primary/15';
}

function formatBytes(bytes: number): string {
  if (bytes < 1024) return `${bytes} B`;
  const units = ['KB', 'MB', 'GB'];
  let value = bytes / 1024;
  let unitIndex = 0;
  while (value >= 1024 && unitIndex < units.length - 1) {
    value /= 1024;
    unitIndex += 1;
  }
  return `${value.toFixed(value >= 10 ? 0 : 1)} ${units[unitIndex]}`;
}

function formatDate(value: string): string {
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return value;
  return date.toLocaleString('zh-CN', { hour12: false });
}
