import {
  Body,
  Controller,
  Delete,
  Get,
  Param,
  Patch,
  Post,
  UploadedFile,
  UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { ApiBearerAuth, ApiConsumes, ApiOperation, ApiTags } from '@nestjs/swagger';
import { ZodResponse } from 'nestjs-zod';
import { ApiCode, Permission } from '@pac/types';
import { RequirePermission } from '../../common/decorators/permissions.decorator';
import { CurrentUser, AuthenticatedUser } from '../../common/decorators/current-user.decorator';
import { BizError } from '../../common/errors/biz-error';
import {
  TenantScope,
  TenantScopeContext,
} from '../../common/decorators/tenant-scope.decorator';
import {
  ColdImportStatusDto,
  ColdImportUploadResponseDto,
  HostDetailDto,
  HostStatsDto,
  HostSummaryDto,
  RemovePushSecretResponseDto,
  RotateCallbackSecretResponseDto,
  RotatePushSecretResponseDto,
  RotateSecretResponseDto,
  UpdateHostRequestDto,
} from './dto/admin.dto';
import { HostsService } from './hosts.service';
import { ImportUploadService } from './import-upload.service';

/// multer 内存模式的最小文件形状(不引 @types/multer,同 assistant.controller)
interface UploadedZip {
  buffer: Buffer;
  originalname: string;
  mimetype: string;
}
/// 上传大小上限:存量分批包压缩后通常几十 MB;留 500MB 余量
const MAX_IMPORT_ZIP_BYTES = 500 * 1024 * 1024;

/**
 * Host Self Admin — 宿主自己的 admin 用户管理"自己这个 host"的配置页。
 *
 * 跟 HostsAdminController(/admin/hosts/:id)区别:
 *   - 本控制器**自动 scope 到调用者的 hostId**(从 token 里的 hostId 取)
 *   - 不接受 :id 参数 — 宿主 admin 永远只能看 / 改自己的 host
 *   - 所有端点都需 PLATFORM_MANAGE 权限(仅 ADMIN 角色)
 *
 * 这层端点专为前端"宿主 admin 配置页"设计。PAC 团队自己用 CLI(pnpm pac:host)更直接。
 */
@ApiTags('admin')
@ApiBearerAuth('accessToken')
@Controller('admin/host/self')
@RequirePermission(Permission.PLATFORM_MANAGE)
export class HostSelfAdminController {
  constructor(
    private readonly hosts: HostsService,
    private readonly importUpload: ImportUploadService,
  ) {}

  @Get()
  @ZodResponse({ status: 200, type: HostDetailDto })
  @ApiOperation({ summary: '当前宿主配置详情(含 pullConfig / push secret meta / 运行状态)' })
  getSelf(@TenantScope() scope: TenantScopeContext) {
    return this.hosts.getDetail(scope.hostId);
  }

  @Get('stats')
  @ZodResponse({ status: 200, type: HostStatsDto })
  @ApiOperation({ summary: '当前宿主运行状态(只刷新指标,不刷新整页用)' })
  getStats(@TenantScope() scope: TenantScopeContext) {
    return this.hosts.getStats(scope.hostId);
  }

  @Patch()
  @ZodResponse({ status: 200, type: HostSummaryDto })
  @ApiOperation({
    summary: '更新当前宿主配置(pullConfig / actionUrls / active)',
    description: '凭据轮换走 rotate 端点;不能改 name / appId',
  })
  update(@TenantScope() scope: TenantScopeContext, @Body() dto: UpdateHostRequestDto) {
    return this.hosts.update(scope.hostId, dto);
  }

  // ─── Secret rotation ───

  @Post('rotate-app-secret')
  @ZodResponse({ status: 200, type: RotateSecretResponseDto })
  @ApiOperation({
    summary: '轮换 appSecret(旧立即失效)',
    description: '返回的 plaintext 仅本次显示,请立即保存。',
  })
  rotateAppSecret(@TenantScope() scope: TenantScopeContext) {
    return this.hosts.rotateAppSecret(scope.hostId);
  }

  @Post('push-secrets')
  @ZodResponse({ status: 200, type: RotatePushSecretResponseDto })
  @ApiOperation({
    summary: '添加新 push secret(数组前插,旧 key 进 grace period)',
    description: '返回的 plaintext 仅本次显示,请立即保存。',
  })
  addPushSecret(@TenantScope() scope: TenantScopeContext) {
    return this.hosts.rotatePushSecret(scope.hostId);
  }

  @Delete('push-secrets/:suffix')
  @ZodResponse({ status: 200, type: RemovePushSecretResponseDto })
  @ApiOperation({
    summary: '按 suffix 删除某条 push secret(不能删唯一一条)',
  })
  removePushSecret(
    @TenantScope() scope: TenantScopeContext,
    @Param('suffix') suffix: string,
  ) {
    return this.hosts.removePushSecretBySuffix(scope.hostId, suffix);
  }

  // ─── 回执密钥(执行结果回调,方向:PAC → 宿主)───

  @Post('callback-secrets')
  @ZodResponse({ status: 200, type: RotateCallbackSecretResponseDto })
  @ApiOperation({
    summary: '添加新 callback secret(数组前插,旧 key 进 grace period)',
    description: '返回的 plaintext 仅本次显示,请立即保存。回执地址(callbackUrl)走 PATCH 设置。',
  })
  addCallbackSecret(@TenantScope() scope: TenantScopeContext) {
    return this.hosts.rotateCallbackSecret(scope.hostId);
  }

  @Delete('callback-secrets/:suffix')
  @ZodResponse({ status: 200, type: RemovePushSecretResponseDto })
  @ApiOperation({
    summary: '按 suffix 删除某条 callback secret(不能删唯一一条)',
  })
  removeCallbackSecret(
    @TenantScope() scope: TenantScopeContext,
    @Param('suffix') suffix: string,
  ) {
    return this.hosts.removeCallbackSecretBySuffix(scope.hostId, suffix);
  }

  // ─── 数据导入(上传压缩包 → 后台 cold-import,方向:宿主 → PAC)───

  @Post('cold-import')
  @ApiConsumes('multipart/form-data')
  @UseInterceptors(
    FileInterceptor('file', { limits: { fileSize: MAX_IMPORT_ZIP_BYTES } }),
  )
  @ZodResponse({ status: 202, type: ColdImportUploadResponseDto })
  @ApiOperation({
    summary: '上传数据压缩包触发整包 cold-import',
    description:
      'zip 内含 manifest.tables 声明的数据文件(CSV/JSON);manifest+assemblers 取自 PAC 代码库不接受上传。' +
      '校验通过后落后台队列,返回 jobId 供轮询进度。',
  })
  async coldImport(
    @TenantScope() scope: TenantScopeContext,
    @CurrentUser() user: AuthenticatedUser,
    @UploadedFile() file: UploadedZip | undefined,
    @Body('dryRun') dryRun?: string,
  ) {
    if (!file?.buffer?.length) {
      throw new BizError(ApiCode.CLIENT_BAD_REQUEST, '未收到上传文件(字段名应为 file)');
    }
    // multipart 文本字段是字符串;'true' → dry-run 预检(不落库)
    return this.importUpload.prepareAndEnqueue(scope, file.buffer, user.sub, dryRun === 'true');
  }

  @Get('cold-import/:jobId')
  @ZodResponse({ status: 200, type: ColdImportStatusDto })
  @ApiOperation({ summary: '查询一次 cold-import 进度(前端轮询,含运行中百分比)' })
  async coldImportStatus(@Param('jobId') jobId: string) {
    return this.importUpload.getProgress(jobId);
  }
}
