import { Injectable } from '@nestjs/common';
import { PrismaService } from '@core/database/prisma/prisma.service';
import { Prisma } from '@prisma/client';

export interface LicenseSummary {
  skuPartNumber: string;
  displayName: string;
  userCount: number;
}
import {
  ListUsersQueryDto,
  SortOrder,
  UsersSortBy,
  UserTimelineQueryDto,
} from '../dto/m365-dormant.dto';
import { M365UserNotFoundException } from '../m365-dormant.exceptions';
import { M365SyncService } from './m365-sync.service';

@Injectable()
export class M365UsersService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly syncService: M365SyncService,
  ) {}

  async list(organizationId: string, query: ListUsersQueryDto) {
    const latest = await this.syncService.findLatestSuccessExecution();
    const inactiveDays = query.inactiveDays ?? 180;
    const includeNever = query.includeNeverSignedIn ?? true;
    const page = query.page ?? 1;
    const pageSize = query.pageSize ?? 50;
    const sortBy = query.sortBy ?? UsersSortBy.daysInactive;
    const sortOrder = query.sortOrder ?? SortOrder.desc;

    const where: Prisma.M365UserWhereInput = { organizationId };

    // 阈值过滤
    const thresholdClause: Prisma.M365UserWhereInput[] = [
      { daysInactive: { gte: inactiveDays } },
    ];
    if (includeNever) {
      thresholdClause.push({ lastAnyActivity: null });
    }
    where.OR = thresholdClause;

    if (query.accountEnabled !== undefined) where.accountEnabled = query.accountEnabled;
    if (query.hasLicense !== undefined) where.hasLicense = query.hasLicense;
    if (query.accountAgeMinDays !== undefined && query.accountAgeMinDays > 0) {
      const cutoff = new Date(Date.now() - query.accountAgeMinDays * 24 * 60 * 60 * 1000);
      where.accountCreatedAt = { lte: cutoff };
    }
    if (query.licenseSkuPartNumber) {
      where.licenses = { array_contains: [{ skuPartNumber: query.licenseSkuPartNumber }] };
    }

    if (query.keyword) {
      // citext 列对 contains 自动 case-insensitive
      where.AND = [
        {
          OR: [
            { userPrincipalName: { contains: query.keyword, mode: 'insensitive' } },
            { displayName: { contains: query.keyword, mode: 'insensitive' } },
            { mail: { contains: query.keyword, mode: 'insensitive' } },
          ],
        },
      ];
    }

    if (query.missingFromLatestSync && latest) {
      where.lastSeenInExecutionId = { not: latest.id };
    }

    const orderBy: Prisma.M365UserOrderByWithRelationInput = {};
    if (sortBy === UsersSortBy.daysInactive) orderBy.daysInactive = sortOrder;
    else if (sortBy === UsersSortBy.lastAnyActivity) orderBy.lastAnyActivity = sortOrder;
    else if (sortBy === UsersSortBy.userPrincipalName) orderBy.userPrincipalName = sortOrder;
    else if (sortBy === UsersSortBy.updatedAt) orderBy.updatedAt = sortOrder;

    const [items, total] = await this.prisma.$transaction([
      this.prisma.m365User.findMany({
        where,
        orderBy,
        skip: (page - 1) * pageSize,
        take: pageSize,
      }),
      this.prisma.m365User.count({ where }),
    ]);

    const itemsWithFlags = items.map((u) => ({
      ...u,
      missingFromLatestSync: latest ? u.lastSeenInExecutionId !== latest.id : false,
    }));

    return {
      snapshotAt: latest?.startedAt ?? null,
      snapshotExecutionId: latest?.id ?? null,
      items: itemsWithFlags,
      pagination: {
        page,
        pageSize,
        total,
        totalPages: Math.ceil(total / pageSize),
      },
    };
  }

  /**
   * 用于 CSV 导出：返回符合筛选条件的全量记录（不分页）
   */
  async listForExport(organizationId: string, query: ListUsersQueryDto, limit: number) {
    const noPaging = { ...query, page: 1, pageSize: limit };
    const result = await this.list(organizationId, noPaging);
    return {
      ...result,
      items: result.items.slice(0, limit),
    };
  }

  /**
   * 列出当前 M365 用户中出现过的所有 license SKU + 各自人数
   * 数据源：M365User.licenses JSON 数组（unwrap 聚合）
   */
  async listLicenseSummary(organizationId: string): Promise<LicenseSummary[]> {
    const rows = await this.prisma.$queryRaw<Array<{
      sku_part_number: string;
      display_name: string;
      user_count: bigint;
    }>>(Prisma.sql`
      SELECT
        license->>'skuPartNumber' AS sku_part_number,
        license->>'displayName'   AS display_name,
        count(DISTINCT u.id)      AS user_count
      FROM platform_ops_center.m365_users u,
           jsonb_array_elements(u.licenses) license
      WHERE u.organization_id = ${organizationId}::uuid
      GROUP BY license->>'skuPartNumber', license->>'displayName'
      ORDER BY user_count DESC
    `);
    return rows.map((r) => ({
      skuPartNumber: r.sku_part_number,
      displayName: r.display_name,
      userCount: Number(r.user_count),
    }));
  }

  async timeline(organizationId: string, userId: string, query: UserTimelineQueryDto) {
    const user = await this.prisma.m365User.findFirst({
      where: { id: userId, organizationId },
    });
    if (!user) throw new M365UserNotFoundException(userId);

    const where: Prisma.M365UserActivityChangeWhereInput = {
      userId: user.id,
      organizationId,
    };
    if (query.field) where.field = query.field;
    if (query.since) where.createdAt = { gte: new Date(query.since) };

    const events = await this.prisma.m365UserActivityChange.findMany({
      where,
      orderBy: { createdAt: 'desc' },
      take: query.limit ?? 100,
    });

    return {
      user: {
        id: user.id,
        userPrincipalName: user.userPrincipalName,
        displayName: user.displayName,
        currentDaysInactive: user.daysInactive,
        firstSeenAt: user.createdAt,
      },
      events: events.map((e) => ({
        id: e.id,
        executionId: e.executionId,
        observedAt: e.createdAt,
        field: e.field,
        previousValue: e.previousValue,
        currentValue: e.currentValue,
      })),
    };
  }
}
