import {
  BadRequestException,
  Body,
  Controller,
  Delete,
  Get,
  HttpCode,
  HttpStatus,
  Logger,
  NotFoundException,
  Param,
  Post,
  Query,
  Request,
  UseGuards,
} from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { PermissionsGuard } from '@modules/organization/auth/guards/permissions.guard';
import { RequirePermissions } from '@common/decorators/permissions.decorator';
import { Auditable } from '@core/observability/audit/decorators/auditable.decorator';
import { PrismaService } from '@core/database/prisma/prisma.service';
import { ContainerHostService } from './services/container-host.service';
import { GiteaClientService } from './services/gitea-client.service';
import {
  InternalAppEventsService,
  EventType,
  type ActorRole,
} from './services/events.service';
import { parseIsoOrUndefined, parsePositiveInt } from '@common/utils/query-parsers';

const RETENTION_DAYS = 30;

/**
 * IT-Admin 管理端（07-api §4.3）
 *
 * 3 个端点全部需 `internal-app:admin` 权限：
 * - GET /admin/apps    — 跨员工列所有 app（分页 + 过滤）
 * - POST /admin/apps/:appId/disable — 强制停用（违规、紧急）
 * - DELETE /admin/apps/:appId — 强制销毁（=owner destroy + 跳鉴权）
 *
 * 所有变更端点走 @Auditable() 自动写 audit-system。
 */
@ApiTags('Internal App Platform - Admin')
@ApiBearerAuth()
@UseGuards(PermissionsGuard)
@Controller('internal-apps/admin')
export class InternalAppAdminController {
  private readonly logger = new Logger(InternalAppAdminController.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly containerHost: ContainerHostService,
    private readonly giteaSvc: GiteaClientService,
    private readonly eventsSvc: InternalAppEventsService,
  ) {}

  /**
   * admin 所在 organization。优先 JWT，回退到 admin 自己的 employee binding。
   * 拿不到直接抛 no_organization，绝不 fallback 到 findFirst()——否则 multi-org 会跨 org 泄露。
   */
  private async resolveAdminOrganizationId(req: {
    user?: { id?: string; userId?: string; organizationId?: string; currentOrganizationId?: string };
  }): Promise<string> {
    let organizationId =
      req.user?.organizationId ?? req.user?.currentOrganizationId;
    if (!organizationId) {
      const adminUserId = req.user?.id ?? req.user?.userId;
      if (adminUserId) {
        const binding = await this.prisma.employeeSlugBinding.findFirst({
          where: { userId: adminUserId },
          select: { organizationId: true },
        });
        organizationId = binding?.organizationId ?? undefined;
      }
    }
    if (!organizationId) {
      throw new BadRequestException({
        code: 'no_organization',
        message: 'admin 用户未关联 organization',
      });
    }
    return organizationId;
  }

  @Get('apps')
  @RequirePermissions('internal-app:admin')
  @ApiOperation({ summary: '列出所有员工 app（跨员工，admin 视图）' })
  async listAllApps(
    @Request() req: { user?: { id?: string; userId?: string; organizationId?: string; currentOrganizationId?: string } },
    @Query('employeeSlug') employeeSlug?: string,
    @Query('status') status?: string,
    @Query('page') pageRaw?: string,
    @Query('pageSize') pageSizeRaw?: string,
  ) {
    const organizationId = await this.resolveAdminOrganizationId(req);
    const page = Math.max(1, parseInt(pageRaw ?? '1', 10) || 1);
    const pageSize = Math.min(100, Math.max(1, parseInt(pageSizeRaw ?? '20', 10) || 20));
    const where: { organizationId: string; employeeSlug?: string; status?: any } = { organizationId };
    if (employeeSlug) where.employeeSlug = employeeSlug;
    if (status) where.status = status;

    const [items, total] = await Promise.all([
      this.prisma.internalApp.findMany({
        where,
        skip: (page - 1) * pageSize,
        take: pageSize,
        orderBy: { lastDeployedAt: 'desc' },
        include: {
          ownerBinding: {
            select: {
              employeeSlug: true,
              sourceMailNickname: true,
              user: { select: { email: true, displayName: true } },
            },
          },
        },
      }),
      this.prisma.internalApp.count({ where }),
    ]);

    return {
      items: items.map((a) => ({
        id: a.id,
        employeeSlug: a.employeeSlug,
        appSlug: a.appSlug,
        displayName: a.displayName,
        runtime: a.runtime,
        status: a.status,
        url: a.url,
        giteaRepoFullName: a.giteaRepoFullName,
        lastDeployedAt: a.lastDeployedAt,
        destroyedAt: a.destroyedAt,
        retentionUntil: a.retentionUntil,
        forceDisabledAt: a.forceDisabledAt,
        forceDisabledReason: a.forceDisabledReason,
        ownerEmail: a.ownerBinding?.user?.email ?? null,
        ownerDisplayName: a.ownerBinding?.user?.displayName ?? null,
      })),
      total,
      page,
      pageSize,
    };
  }

  @Post('apps/:appId/disable')
  @RequirePermissions('internal-app:admin')
  @Auditable()
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: '强制停用 app（违规 / 紧急下线）' })
  async forceDisable(
    @Param('appId') appId: string,
    @Body() body: { reason?: string },
    @Request() req: { user?: { id?: string; userId?: string } },
  ) {
    const adminUserId = req.user?.id ?? req.user?.userId ?? null;
    const reason = (body?.reason ?? '').trim();
    if (!reason) {
      throw new BadRequestException({
        code: 'reason_required',
        message: 'reason 字段必填（≤500 字符，记入 audit）',
      });
    }
    if (reason.length > 500) {
      throw new BadRequestException({
        code: 'reason_too_long',
        message: 'reason ≤ 500 字符',
      });
    }

    const app = await this.prisma.internalApp.findUnique({ where: { id: appId } });
    if (!app) throw new NotFoundException({ code: 'app_not_found' });
    if (
      app.status === 'DESTROYED' ||
      app.status === 'PURGED' ||
      app.status === 'DISABLED_ARCHIVED'
    ) {
      throw new BadRequestException({
        code: 'app_not_disable_target',
        message: `app 当前 ${app.status}，不需强制停用`,
      });
    }

    // 容器 + Caddy 路由先撤（员工 / 同事立即看不到）
    const teardown = await this.containerHost.destroyContainer(
      app.employeeSlug,
      app.appSlug,
    );
    if (!teardown.ok) {
      this.logger.error(`forceDisable teardown 失败 appId=${appId}: ${teardown.code}`);
    }

    const now = new Date();
    await this.prisma.internalApp.update({
      where: { id: appId },
      data: {
        status: 'DISABLED_ARCHIVED',
        forceDisabledAt: now,
        forceDisabledReason: reason,
        forceDisabledById: adminUserId,
      },
    });

    this.logger.warn(
      `[IT-ADMIN] forceDisable appId=${appId} employeeSlug=${app.employeeSlug} reason="${reason}"`,
    );
    await this.eventsSvc.emit({
      eventType: EventType.APP_DISABLED_BY_ADMIN,
      actorRole: 'ADMIN',
      organizationId: app.organizationId,
      appId,
      employeeSlug: app.employeeSlug,
      actorId: adminUserId ?? undefined,
      payload: { reason, teardownOk: teardown.ok },
    });
    return {
      appId,
      status: 'DISABLED_ARCHIVED' as const,
      forceDisabledAt: now.toISOString(),
      reason,
    };
  }

  @Delete('apps/:appId')
  @RequirePermissions('internal-app:admin')
  @Auditable()
  @ApiOperation({ summary: '强制销毁 app（=owner destroy + 跳鉴权）' })
  async forceDestroy(
    @Param('appId') appId: string,
    @Request() req: { user?: { id?: string; userId?: string } },
    @Query('reason') reason?: string,
  ) {
    const adminUserId = req.user?.id ?? req.user?.userId ?? null;
    const trimmedReason = (reason ?? '').trim();
    if (trimmedReason.length > 500) {
      throw new BadRequestException({
        code: 'reason_too_long',
        message: 'reason ≤ 500 字符',
      });
    }
    const app = await this.prisma.internalApp.findUnique({ where: { id: appId } });
    if (!app) throw new NotFoundException({ code: 'app_not_found' });
    if (app.status === 'DESTROYED' || app.status === 'PURGED') {
      throw new BadRequestException({
        code: 'app_already_destroyed',
        message: `app 已是 ${app.status} 状态`,
      });
    }

    // 1. 容器 + Caddy
    const teardown = await this.containerHost.destroyContainer(
      app.employeeSlug,
      app.appSlug,
    );
    if (!teardown.ok) {
      this.logger.error(
        `forceDestroy teardown 失败 appId=${appId}: ${teardown.code} ${teardown.message}`,
      );
    }

    // 2. Gitea 归档（容错——同 owner destroy 行为）
    const archive = await this.giteaSvc.archiveRepo({
      employeeSlug: app.employeeSlug,
      appSlug: app.appSlug,
    });
    if (!archive.ok) {
      this.logger.warn(
        `forceDestroy archive 失败但继续: ${archive.error.code}`,
      );
    }

    // 3. DB 状态机
    const now = new Date();
    const retentionUntil = new Date(
      now.getTime() + RETENTION_DAYS * 24 * 3600 * 1000,
    );
    const persistedReason = trimmedReason || 'IT-Admin force destroy';
    await this.prisma.internalApp.update({
      where: { id: appId },
      data: {
        status: 'DESTROYED',
        destroyedAt: now,
        retentionUntil,
        forceDisabledReason: persistedReason,
      },
    });

    this.logger.warn(
      `[IT-ADMIN] forceDestroy appId=${appId} employeeSlug=${app.employeeSlug} reason="${trimmedReason}"`,
    );
    await this.eventsSvc.emit({
      eventType: EventType.APP_FORCE_DESTROYED_BY_ADMIN,
      actorRole: 'ADMIN',
      organizationId: app.organizationId,
      appId,
      employeeSlug: app.employeeSlug,
      actorId: adminUserId ?? undefined,
      payload: {
        reason: trimmedReason || null,
        retentionUntil: retentionUntil.toISOString(),
        teardownOk: teardown.ok,
        archiveOk: archive.ok,
      },
    });
    return {
      appId,
      status: 'DESTROYED' as const,
      destroyedAt: now.toISOString(),
      retentionUntil: retentionUntil.toISOString(),
      reason: trimmedReason || null,
    };
  }

  @Get('events')
  @RequirePermissions('internal-app:admin')
  @ApiOperation({ summary: '全员事件流（admin 视图，07-api §4.5）' })
  async listEvents(
    @Request() req: { user?: { id?: string; userId?: string; organizationId?: string; currentOrganizationId?: string } },
    @Query('appId') appId?: string,
    @Query('employeeSlug') employeeSlug?: string,
    @Query('actorRole') actorRole?: string,
    @Query('eventType') eventType?: string,
    @Query('outcome') outcome?: string,
    @Query('from') from?: string,
    @Query('to') to?: string,
    @Query('page') page?: string,
    @Query('pageSize') pageSize?: string,
  ) {
    const organizationId = await this.resolveAdminOrganizationId(req);

    if (actorRole && !['OWNER', 'ADMIN', 'SYSTEM'].includes(actorRole)) {
      throw new BadRequestException({ code: 'invalid_query', message: `actorRole 不合法: ${actorRole}` });
    }
    if (outcome && !['OK', 'FAIL'].includes(outcome)) {
      throw new BadRequestException({ code: 'invalid_query', message: `outcome 不合法: ${outcome}` });
    }

    return this.eventsSvc.list({
      organizationId,
      appId: appId?.trim() || undefined,
      employeeSlug: employeeSlug?.trim() || undefined,
      actorRole: actorRole as ActorRole | undefined,
      eventTypes: eventType
        ? eventType.split(',').map((s) => s.trim()).filter(Boolean)
        : undefined,
      outcome: outcome as 'OK' | 'FAIL' | undefined,
      from: parseIsoOrUndefined(from),
      to: parseIsoOrUndefined(to),
      page: parsePositiveInt(page),
      pageSize: parsePositiveInt(pageSize),
    });
  }
}

