import {
  Body,
  Controller,
  Get,
  HttpCode,
  Param,
  ParseUUIDPipe,
  Post,
  Query,
  Req,
  Res,
} from '@nestjs/common';
import { RequirePermissions } from '@/common/decorators/permissions.decorator';
import { CurrentUser } from '@/common/decorators/current-user.decorator';
import { Auditable, Sensitive } from '@core/observability/audit/decorators/auditable.decorator';
import { AiUsageTokenService } from '../services/token.service';
import { AiUsageDashboardService } from '../services/dashboard.service';
import { AiUsageDeviceService } from '../services/device.service';
import {
  BlockDeviceDto,
  UnblockDeviceDto,
  RevokeTokenDto,
  DashboardQueryDto,
  DeviceQueryDto,
  TokenQueryDto,
} from '../dto';
import type { Response } from 'express';

/** Quote-wrap + double-quote-escape per RFC 4180 */
function csvCell(v: string | number | null | undefined): string {
  const s = String(v ?? '');
  return `"${s.replace(/"/g, '""')}"`;
}

@Controller('ai-usage')
export class AiUsageAdminController {
  constructor(
    private readonly tokenSvc: AiUsageTokenService,
    private readonly dashboardSvc: AiUsageDashboardService,
    private readonly deviceSvc: AiUsageDeviceService,
  ) {}

  // ===== Admin dashboard =====
  @Get('summary')
  @RequirePermissions('ai-usage:view-all')
  async summary(@Query() q: DashboardQueryDto, @Req() req: any) {
    const organizationId = req.user?.currentOrganizationId;
    const range = this.dashboardSvc.resolveRange(q.period, q.from, q.to);
    return this.dashboardSvc.summary({ organizationId }, range);
  }

  @Get('trend')
  @RequirePermissions('ai-usage:view-all')
  async trend(@Query() q: DashboardQueryDto, @Req() req: any) {
    const organizationId = req.user?.currentOrganizationId;
    const range = this.dashboardSvc.resolveRange(q.period, q.from, q.to);
    return this.dashboardSvc.trend({ organizationId }, range, q.granularity ?? 'day', (q.groupBy as any) ?? 'none');
  }

  @Get('breakdown')
  @RequirePermissions('ai-usage:view-all')
  async breakdown(@Query() q: DashboardQueryDto, @Req() req: any) {
    const organizationId = req.user?.currentOrganizationId;
    const range = this.dashboardSvc.resolveRange(q.period, q.from, q.to);
    return this.dashboardSvc.breakdown(
      { organizationId },
      range,
      (q.by ?? 'user') as any,
      q.page ?? 1,
      q.pageSize ?? 20,
    );
  }

  // ===== v1.1 富 metadata 聚合 =====

  @Get('tool-frequency')
  @RequirePermissions('ai-usage:view-all')
  async toolFrequency(@Query() q: DashboardQueryDto, @Req() req: any) {
    const organizationId = req.user?.currentOrganizationId;
    const range = this.dashboardSvc.resolveRange(q.period, q.from, q.to);
    return this.dashboardSvc.toolFrequency({ organizationId }, range);
  }

  @Get('session-stats')
  @RequirePermissions('ai-usage:view-all')
  async sessionStats(@Query() q: DashboardQueryDto & { userId?: string; projectBasename?: string }, @Req() req: any) {
    const organizationId = req.user?.currentOrganizationId;
    const range = this.dashboardSvc.resolveRange(q.period, q.from, q.to);
    return this.dashboardSvc.sessionStats(
      { organizationId },
      range,
      { userId: q.userId, projectBasename: q.projectBasename },
      q.page ?? 1,
      q.pageSize ?? 50,
    );
  }

  @Get('sessions/:sessionId/turns')
  @RequirePermissions('ai-usage:view-all')
  async sessionTurns(@Param('sessionId') sessionId: string, @Req() req: any) {
    const organizationId = req.user?.currentOrganizationId;
    return this.dashboardSvc.sessionTurns({ organizationId }, sessionId);
  }

  @Get('turn-gap-distribution')
  @RequirePermissions('ai-usage:view-all')
  async turnGapDistribution(@Query() q: DashboardQueryDto, @Req() req: any) {
    const organizationId = req.user?.currentOrganizationId;
    const range = this.dashboardSvc.resolveRange(q.period, q.from, q.to);
    return this.dashboardSvc.turnGapDistribution({ organizationId }, range);
  }

  @Get('service-tier-mix')
  @RequirePermissions('ai-usage:view-all')
  async serviceTierMix(@Query() q: DashboardQueryDto, @Req() req: any) {
    const organizationId = req.user?.currentOrganizationId;
    const range = this.dashboardSvc.resolveRange(q.period, q.from, q.to);
    return this.dashboardSvc.serviceTierMix({ organizationId }, range);
  }

  @Get('stop-reason-mix')
  @RequirePermissions('ai-usage:view-all')
  async stopReasonMix(@Query() q: DashboardQueryDto, @Req() req: any) {
    const organizationId = req.user?.currentOrganizationId;
    const range = this.dashboardSvc.resolveRange(q.period, q.from, q.to);
    return this.dashboardSvc.stopReasonMix({ organizationId }, range);
  }

  @Get('daily-user-matrix')
  @RequirePermissions('ai-usage:view-all')
  async dailyUserMatrix(@Query() q: DashboardQueryDto, @Req() req: any) {
    const organizationId = req.user?.currentOrganizationId;
    const range = this.dashboardSvc.resolveRange(q.period, q.from, q.to);
    return this.dashboardSvc.dailyUserMatrix({ organizationId }, range);
  }

  @Get('git-branch-heatmap')
  @RequirePermissions('ai-usage:view-all')
  async gitBranchHeatmap(@Query() q: DashboardQueryDto, @Req() req: any) {
    const organizationId = req.user?.currentOrganizationId;
    const range = this.dashboardSvc.resolveRange(q.period, q.from, q.to);
    return this.dashboardSvc.gitBranchHeatmap({ organizationId }, range);
  }

  @Get('export')
  @RequirePermissions('ai-usage:export')
  async exportCsv(
    @Query() q: DashboardQueryDto & { withRich?: string; format?: string },
    @Req() req: any,
    @Res() res: Response,
  ) {
    const organizationId = req.user?.currentOrganizationId;
    const range = this.dashboardSvc.resolveRange(q.period, q.from, q.to);

    // 富 metadata 详细行导出（每个 event 一行）— 用 withRich=1 切换
    if (q.withRich === '1' || q.withRich === 'true') {
      const events = await this.dashboardSvc.exportRichEvents({ organizationId }, range, 10000);
      res.setHeader('Content-Type', 'text/csv; charset=utf-8');
      res.setHeader(
        'Content-Disposition',
        `attachment; filename="ai-usage-rich-${range.from.toISOString().slice(0, 10)}.csv"`,
      );
      res.write('﻿'); // UTF-8 BOM
      res.write(
        'ts,user,project_basename,session_id,turn_index,tool,model,input_tokens,output_tokens,cache_creation_tokens,cache_read_tokens,total_tokens,cost_usd,tool_use_count,tool_names,stop_reason,service_tier,git_branch,worktree_label,agent_version_event\n',
      );
      for (const e of events) {
        res.write(
          [
            csvCell(e.ts),
            csvCell(e.userLabel),
            csvCell(e.projectBasename),
            csvCell(e.sessionId),
            e.turnIndex ?? '',
            csvCell(e.tool),
            csvCell(e.model),
            e.inputTokens,
            e.outputTokens,
            e.cacheCreationTokens,
            e.cacheReadTokens,
            e.totalTokens,
            e.costUsd,
            e.toolUseCount ?? '',
            csvCell(Array.isArray(e.toolNames) ? e.toolNames.join('|') : ''),
            csvCell(e.stopReason ?? ''),
            csvCell(e.serviceTier ?? ''),
            csvCell(e.gitBranch ?? ''),
            csvCell(e.worktreeLabel ?? ''),
            csvCell(e.agentVersionEvent ?? ''),
          ].join(',') + '\n',
        );
      }
      res.end();
      return;
    }

    // 标准 breakdown 导出（向后兼容）
    const breakdown = await this.dashboardSvc.breakdown(
      { organizationId },
      range,
      (q.by ?? 'user') as any,
      1,
      1000,
    );
    res.setHeader('Content-Type', 'text/csv; charset=utf-8');
    res.setHeader(
      'Content-Disposition',
      `attachment; filename="ai-usage-${q.by ?? 'user'}-${range.from.toISOString().slice(0, 10)}.csv"`,
    );
    res.write('﻿'); // UTF-8 BOM
    res.write(`${q.by ?? 'user'},tokens,cost_usd,share\n`);
    for (const row of breakdown.items) {
      res.write(`${csvCell(row.key)},${row.tokens},${row.costUsd},${row.share.toFixed(4)}\n`);
    }
    res.end();
  }

  // ===== Admin device management =====
  @Get('devices')
  @RequirePermissions('ai-usage:view-all')
  async listDevices(@Query() q: DeviceQueryDto, @Req() req: any) {
    const organizationId = req.user?.currentOrganizationId;
    const page = q.page ?? 1;
    const pageSize = q.pageSize ?? 20;
    const { items, total } = await this.deviceSvc.list({
      organizationId,
      status: q.status,
      q: q.q,
      platform: q.platform,
      skip: (page - 1) * pageSize,
      take: pageSize,
    });
    return { items, pagination: { page, pageSize, total, totalPages: Math.ceil(total / pageSize) } };
  }

  @Post('devices/:id/block')
  @Auditable()
  @Sensitive()
  @RequirePermissions('ai-usage:block-device')
  async blockDevice(
    @Param('id', ParseUUIDPipe) id: string,
    @Body() dto: BlockDeviceDto,
    @CurrentUser('userId') adminId: string,
    @Req() req: any,
  ) {
    const organizationId = req.user?.currentOrganizationId;
    return this.deviceSvc.block({ deviceId: id, organizationId, adminId, reason: dto.reason });
  }

  @Post('devices/:id/unblock')
  @Auditable()
  @Sensitive()
  @RequirePermissions('ai-usage:block-device')
  async unblockDevice(
    @Param('id', ParseUUIDPipe) id: string,
    @Body() _dto: UnblockDeviceDto,
    @Req() req: any,
  ) {
    const organizationId = req.user?.currentOrganizationId;
    return this.deviceSvc.unblock({ deviceId: id, organizationId });
  }

  // ===== Admin token management =====
  @Get('tokens')
  @RequirePermissions('ai-usage:manage-tokens-all')
  async listTokens(@Query() q: TokenQueryDto, @Req() req: any) {
    const organizationId = req.user?.currentOrganizationId;
    const page = q.page ?? 1;
    const pageSize = q.pageSize ?? 20;
    const { items, total } = await this.tokenSvc.listAll({
      organizationId,
      userId: q.userId,
      status: q.status,
      skip: (page - 1) * pageSize,
      take: pageSize,
    });
    return { items, pagination: { page, pageSize, total, totalPages: Math.ceil(total / pageSize) } };
  }

  @Post('tokens/:id/revoke')
  @Auditable()
  @Sensitive()
  @HttpCode(200)
  @RequirePermissions('ai-usage:manage-tokens-all')
  async adminRevokeToken(
    @Param('id', ParseUUIDPipe) id: string,
    @Body() _dto: RevokeTokenDto,
    @CurrentUser('userId') adminId: string,
    @Req() req: any,
  ) {
    const organizationId = req.user?.currentOrganizationId;
    return this.tokenSvc.revoke({
      tokenId: id,
      userId: '',
      isAdmin: true,
      revokedById: adminId,
      organizationId,
    });
  }

  // ===== Admin DLQ =====
  @Get('dlq')
  @RequirePermissions('ai-usage:view-all')
  async listDlq(@Query('reason') reason: string, @Query('page') page: string, @Query('pageSize') pageSize: string, @Req() req: any) {
    const organizationId = req.user?.currentOrganizationId;
    const p = parseInt(page) || 1;
    const ps = parseInt(pageSize) || 20;
    const { items, total } = await this.deviceSvc.listDlq({
      organizationId,
      reason,
      skip: (p - 1) * ps,
      take: ps,
    });
    return { items, pagination: { page: p, pageSize: ps, total, totalPages: Math.ceil(total / ps) } };
  }
}
