import { spawn } from 'node:child_process';
import { createHash, randomUUID } from 'node:crypto';
import fs from 'node:fs/promises';
import path from 'node:path';
import { NextRequest, NextResponse } from 'next/server';
import { requireAdmin } from '@/lib/admin-auth';

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';

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

type UpgradeJobState = {
  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[];
};

const MAX_PACKAGE_BYTES = 300 * 1024 * 1024;
const RUNNING_STATUSES = new Set<UpgradeStatus>(['queued', 'running', 'rolling_back']);

export async function GET(request: NextRequest) {
  const authError = await requireAdmin(request);
  if (authError) return authError;

  try {
    const states = await readStates();
    return NextResponse.json({
      latest: states[0] || null,
      history: states.slice(0, 12),
      stateDir: getUpgradeStateRoot(),
      running: states.some(job => RUNNING_STATUSES.has(job.status)),
    });
  } catch (error) {
    console.error('[admin/upgrade] failed to read state:', error);
    return NextResponse.json({ error: '读取升级状态失败' }, { status: 500 });
  }
}

export async function POST(request: NextRequest) {
  const authError = await requireAdmin(request);
  if (authError) return authError;

  try {
    const states = await readStates();
    const runningJob = states.find(job => RUNNING_STATUSES.has(job.status));
    if (runningJob) {
      return NextResponse.json({ error: `已有升级任务正在执行：${runningJob.id}` }, { status: 409 });
    }

    const form = await request.formData();
    const modeValue = String(form.get('mode') || '');
    const mode = modeValue === 'hot' || modeValue === 'cold' ? modeValue : null;
    if (!mode) {
      return NextResponse.json({ error: '请选择热更新或冷更新' }, { status: 400 });
    }

    const file = form.get('package');
    if (!(file instanceof File)) {
      return NextResponse.json({ error: '请上传升级包' }, { status: 400 });
    }
    if (file.size <= 0) {
      return NextResponse.json({ error: '升级包为空' }, { status: 400 });
    }
    if (file.size > MAX_PACKAGE_BYTES) {
      return NextResponse.json({ error: '升级包不能超过 300MB' }, { status: 400 });
    }
    if (!isAllowedArchiveName(file.name)) {
      return NextResponse.json({ error: '仅支持 .tar、.tar.gz、.tgz 升级包' }, { status: 400 });
    }

    const stateRoot = getUpgradeStateRoot();
    const jobId = `${new Date().toISOString().replace(/[-:.TZ]/g, '').slice(0, 14)}-${randomUUID().slice(0, 8)}`;
    const jobDir = path.join(stateRoot, 'jobs', jobId);
    const uploadDir = path.join(jobDir, 'upload');
    await fs.mkdir(uploadDir, { recursive: true, mode: 0o700 });

    const safeName = sanitizeFileName(file.name);
    const packagePath = path.join(uploadDir, safeName);
    const bytes = Buffer.from(await file.arrayBuffer());
    await fs.writeFile(packagePath, bytes, { mode: 0o600 });

    const now = new Date().toISOString();
    const initialState: UpgradeJobState = {
      id: jobId,
      mode,
      status: 'queued',
      step: 'queued',
      message: '升级包已上传，等待执行',
      progress: 0,
      packageName: file.name,
      packageHash: createHash('sha256').update(bytes).digest('hex'),
      startedAt: now,
      updatedAt: now,
      logs: [`[${now}] 上传升级包 ${file.name} (${file.size} bytes)`],
    };
    await writeState(jobDir, initialState);

    const child = spawn(process.execPath, [
      path.join(process.cwd(), 'scripts/admin-upgrade-runner.mjs'),
      '--job-id',
      jobId,
      '--mode',
      mode,
      '--package',
      packagePath,
      '--package-name',
      file.name,
      '--project',
      process.cwd(),
    ], {
      cwd: process.cwd(),
      detached: true,
      stdio: 'ignore',
      env: {
        ...process.env,
        UPGRADE_STATE_DIR: stateRoot,
        COREPACK_HOME: process.env.COREPACK_HOME || '/tmp/corepack',
      },
    });
    child.unref();

    return NextResponse.json({ success: true, job: initialState });
  } catch (error) {
    console.error('[admin/upgrade] failed to start upgrade:', error);
    return NextResponse.json({ error: error instanceof Error ? error.message : '创建升级任务失败' }, { status: 500 });
  }
}

function getUpgradeStateRoot(): string {
  const configured = process.env.UPGRADE_STATE_DIR;
  if (configured) return path.resolve(configured);
  if (process.env.LOCAL_STORAGE_DIR) return path.join(path.dirname(process.env.LOCAL_STORAGE_DIR), 'upgrade');
  return path.join(process.cwd(), 'upgrade-state');
}

async function readStates(): Promise<UpgradeJobState[]> {
  const jobsRoot = path.join(getUpgradeStateRoot(), 'jobs');
  let jobNames: string[] = [];
  try {
    jobNames = await fs.readdir(jobsRoot);
  } catch {
    return [];
  }

  const states = await Promise.all(
    jobNames.map(async jobName => {
      try {
        const statePath = path.join(jobsRoot, jobName, 'state.json');
        const raw = await fs.readFile(statePath, 'utf8');
        return JSON.parse(raw) as UpgradeJobState;
      } catch {
        return null;
      }
    }),
  );

  return states
    .filter((job): job is UpgradeJobState => Boolean(job))
    .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
}

async function writeState(jobDir: string, state: UpgradeJobState): Promise<void> {
  await fs.mkdir(jobDir, { recursive: true, mode: 0o700 });
  await fs.writeFile(path.join(jobDir, 'state.json'), `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600 });
}

function isAllowedArchiveName(name: string): boolean {
  return name.endsWith('.tar') || name.endsWith('.tar.gz') || name.endsWith('.tgz');
}

function sanitizeFileName(name: string): string {
  const baseName = path.basename(name).replace(/[^a-zA-Z0-9._-]/g, '_');
  return baseName || 'upgrade-package.tar.gz';
}
