import {
  Body,
  Controller,
  Get,
  HttpCode,
  HttpStatus,
  Param,
  ParseUUIDPipe,
  Post,
  Query,
  Req,
  Res,
  UseGuards,
} from '@nestjs/common';
import type { Request, Response } from 'express';
import { PermissionsGuard } from '../../../organization/auth/guards/permissions.guard';
import { RequirePermissions } from '@common/decorators/permissions.decorator';
import { Auditable, Sensitive } from '@core/observability/audit/decorators/auditable.decorator';
import { M365SyncService } from '../services/m365-sync.service';
import { M365UsersService } from '../services/m365-users.service';
import {
  ExecutionResultPayload,
  HistoryQueryDto,
  ListUsersQueryDto,
  UsersFormat,
  UserTimelineQueryDto,
} from '../dto/m365-dormant.dto';
import {
  ExportTooLargeException,
  NoSuccessfulSyncException,
} from '../m365-dormant.exceptions';

const CSV_ROW_LIMIT = 50000;

/**
 * v1 单租户假设：用户无 currentOrganizationId（如 system-level itadmin）时，
 * 回退到系统级 sentinel UUID，避免每个用户都需要先选组织。
 * v1.x 多租户场景下，请求需带真实 organizationId。
 */
const SYSTEM_ORG_SENTINEL = '00000000-0000-0000-0000-000000000000';

function resolveOrgId(user: any): string {
  return user?.currentOrganizationId ?? SYSTEM_ORG_SENTINEL;
}

@Controller('ops-center/m365-dormant')
@UseGuards(PermissionsGuard)
export class M365DormantController {
  constructor(
    private readonly syncService: M365SyncService,
    private readonly usersService: M365UsersService,
  ) {}

  @Get('users')
  @RequirePermissions('m365-dormant:read')
  async listUsers(
    @Query() query: ListUsersQueryDto,
    @Req() req: Request,
    @Res({ passthrough: true }) res: Response,
  ) {
    const organizationId = resolveOrgId((req as any).user);

    if (query.format === UsersFormat.csv) {
      return this.exportCsv(organizationId, query, req, res);
    }

    const latest = await this.syncService.findLatestSuccessExecution();
    if (!latest) {
      throw new NoSuccessfulSyncException();
    }
    return this.usersService.list(organizationId, query);
  }

  /** Distinct licenses currently in use + user counts (drives frontend filter dropdown). */
  @Get('licenses')
  @RequirePermissions('m365-dormant:read')
  async listLicenses(@Req() req: Request) {
    const organizationId = resolveOrgId((req as any).user);
    const items = await this.usersService.listLicenseSummary(organizationId);
    return { items };
  }

  @Get('users/:userId/timeline')
  @RequirePermissions('m365-dormant:read')
  async getUserTimeline(
    @Param('userId', ParseUUIDPipe) userId: string,
    @Query() query: UserTimelineQueryDto,
    @Req() req: Request,
  ) {
    const organizationId = resolveOrgId((req as any).user);
    return this.usersService.timeline(organizationId, userId, query);
  }

  @Post('sync')
  @HttpCode(HttpStatus.ACCEPTED)
  @RequirePermissions('m365-dormant:sync')
  @Auditable()
  @Sensitive()
  async triggerSync(@Req() req: Request) {
    const user = (req as any).user;
    return this.syncService.triggerSync(user.userId, resolveOrgId(user));
  }

  @Get('sync/latest')
  @RequirePermissions('m365-dormant:read')
  async getLatestSync() {
    const exec = await this.syncService.findLatestExecution();
    if (!exec) return null;
    return this.serializeExecution(exec);
  }

  @Get('sync/history')
  @RequirePermissions('m365-dormant:read')
  async getSyncHistory(@Query() query: HistoryQueryDto) {
    const items = await this.syncService.findHistory(query.limit ?? 30, query.status);
    return { items: items.map((e) => this.serializeExecution(e)) };
  }

  private serializeExecution(exec: any) {
    const result: ExecutionResultPayload | null = exec.result ?? null;
    return {
      id: exec.id,
      status: exec.status,
      triggerSource: exec.triggerType,
      createdById: exec.triggeredBy,
      startedAt: exec.startedAt,
      finishedAt: exec.completedAt,
      duration: exec.duration,
      totalUsers: result?.totalUsers ?? null,
      changedUsers: result?.changedUsers ?? null,
      inactiveDistribution: result?.inactiveDistribution ?? null,
      errorCode: this.parseErrorCode(exec.error),
      errorMessage: this.parseErrorMessage(exec.error),
    };
  }

  private parseErrorCode(raw: string | null): string | null {
    if (!raw) return null;
    const m = raw.match(/^([A-Z_]+):/);
    return m ? m[1] : null;
  }

  private parseErrorMessage(raw: string | null): string | null {
    if (!raw) return null;
    const idx = raw.indexOf(':');
    return idx >= 0 ? raw.slice(idx + 1).trim() : raw;
  }

  private async exportCsv(
    organizationId: string,
    query: ListUsersQueryDto,
    req: Request,
    res: Response,
  ) {
    // CSV 导出额外要求 export 权限——通过装饰器无法做到分支检查，这里手动校验
    const userPerms: string[] = (req as any).user?.permissions ?? [];
    const allowed =
      userPerms.includes('m365-dormant:export') ||
      userPerms.includes('m365-dormant:*') ||
      userPerms.includes('*:*');
    if (!allowed) {
      res.status(403).json({
        success: false,
        error: { code: 'FORBIDDEN', message: 'Missing m365-dormant:export permission' },
      });
      return;
    }

    const latest = await this.syncService.findLatestSuccessExecution();
    if (!latest) throw new NoSuccessfulSyncException();

    const result = await this.usersService.listForExport(organizationId, query, CSV_ROW_LIMIT + 1);
    if (result.pagination.total > CSV_ROW_LIMIT) {
      throw new ExportTooLargeException(result.pagination.total, CSV_ROW_LIMIT);
    }

    const csv = renderCsv(result.items);
    const filename = `m365-dormant-${formatYmd(latest.startedAt)}.csv`;
    res.setHeader('Content-Type', 'text/csv; charset=utf-8');
    res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
    res.write('﻿'); // UTF-8 BOM
    res.end(csv);
  }
}

const CSV_COLUMNS: Array<{ key: string; header: string }> = [
  { key: 'userPrincipalName', header: 'UserPrincipalName' },
  { key: 'displayName', header: 'DisplayName' },
  { key: 'mail', header: 'Mail' },
  { key: 'department', header: 'Department' },
  { key: 'jobTitle', header: 'JobTitle' },
  { key: 'accountEnabled', header: 'AccountEnabled' },
  { key: 'hasLicense', header: 'HasLicense' },
  { key: 'lastSignInDateTime', header: 'LastSignIn' },
  { key: 'lastNonInteractiveSignInDateTime', header: 'LastNonInteractiveSignIn' },
  { key: 'lastEmailActivity', header: 'LastEmailActivity' },
  { key: 'lastOneDriveActivity', header: 'LastOneDriveActivity' },
  { key: 'lastTeamsActivity', header: 'LastTeamsActivity' },
  { key: 'lastSharePointActivity', header: 'LastSharePointActivity' },
  { key: 'lastAnyActivity', header: 'LastAnyActivity' },
  { key: 'daysInactive', header: 'DaysInactive' },
];

function renderCsv(items: any[]): string {
  const lines = [CSV_COLUMNS.map((c) => csvEscape(c.header)).join(',')];
  for (const item of items) {
    lines.push(CSV_COLUMNS.map((c) => csvEscape(formatCell(item[c.key]))).join(','));
  }
  return lines.join('\r\n');
}

function csvEscape(raw: string): string {
  if (raw == null) return '';
  if (/[",\r\n]/.test(raw)) {
    return `"${raw.replace(/"/g, '""')}"`;
  }
  return raw;
}

function formatCell(v: any): string {
  if (v === null || v === undefined) return '';
  if (v instanceof Date) return v.toISOString();
  return String(v);
}

function formatYmd(d: Date): string {
  const y = d.getUTCFullYear();
  const m = String(d.getUTCMonth() + 1).padStart(2, '0');
  const day = String(d.getUTCDate()).padStart(2, '0');
  return `${y}${m}${day}`;
}
