import { Injectable, ConflictException, ForbiddenException, NotFoundException } from '@nestjs/common';
import { PrismaService } from '@core/database/prisma/prisma.service';
import { randomBytes, createHash } from 'crypto';
import * as bcrypt from 'bcrypt';
import { SkipAssertAccess } from '@common/decorators/skip-assert-access.decorator';

const TOKEN_PREFIX = 'ffai_';
const TOKEN_RANDOM_BYTES = 24; // base64url ≈ 32 chars → total ~37 chars with prefix
const BCRYPT_ROUNDS = 12;
const PREFIX_VISIBLE_LEN = 12; // ffai_ + 7 random chars shown in UI

@Injectable()
export class AiUsageTokenService {
  constructor(private readonly prisma: PrismaService) {}

  /**
   * 生成新 token。明文 token 仅在本调用返回值中暴露一次。
   */
  async create(params: {
    userId: string;
    organizationId: string;
    name: string;
  }): Promise<{
    id: string;
    name: string;
    prefix: string;
    token: string;
    createdAt: Date;
  }> {
    if (!params.organizationId) {
      throw new ForbiddenException('AI_USAGE_ORG_CONTEXT_REQUIRED');
    }
    const random = randomBytes(TOKEN_RANDOM_BYTES).toString('base64url');
    const token = `${TOKEN_PREFIX}${random}`;
    const prefix = token.slice(0, PREFIX_VISIBLE_LEN);
    const tokenHash = await bcrypt.hash(token, BCRYPT_ROUNDS);

    const row = await this.prisma.aiUsageToken.create({
      data: {
        userId: params.userId,
        organizationId: params.organizationId,
        createdById: params.userId,
        name: params.name,
        prefix,
        tokenHash,
      },
    });

    return {
      id: row.id,
      name: row.name,
      prefix: row.prefix,
      token,
      createdAt: row.createdAt,
    };
  }

  /**
   * 列出员工自己的 token（含已撤销，UI 可筛选）
   */
  async listForUser(userId: string) {
    return this.prisma.aiUsageToken.findMany({
      where: { userId },
      orderBy: { createdAt: 'desc' },
      select: {
        id: true,
        name: true,
        prefix: true,
        lastUsedAt: true,
        lastUsedIp: true,
        createdAt: true,
        revokedAt: true,
      },
    });
  }

  @SkipAssertAccess('non-admin 路径强制 userId 过滤；admin 路径强制 organizationId 过滤 + manage-tokens-all 权限')
  async revoke(params: {
    tokenId: string;
    userId: string;
    isAdmin: boolean;
    revokedById: string;
    organizationId?: string;
  }) {
    let where: any;
    if (params.isAdmin) {
      // admin 路径：必须带当前 org，避免跨 org 撤销 token；缺 org → 403
      if (!params.organizationId) {
        throw new ForbiddenException('AI_USAGE_ORG_CONTEXT_REQUIRED');
      }
      where = { id: params.tokenId, organizationId: params.organizationId };
    } else {
      where = { id: params.tokenId, userId: params.userId };
    }
    const found = await this.prisma.aiUsageToken.findFirst({ where });
    if (!found) throw new NotFoundException('AI_USAGE_TOKEN_NOT_FOUND');
    if (found.revokedAt) throw new ConflictException('AI_USAGE_TOKEN_ALREADY_REVOKED');
    return this.prisma.aiUsageToken.update({
      where: { id: found.id },
      data: { revokedAt: new Date(), revokedById: params.revokedById },
    });
  }

  /**
   * 通过明文 token 解析：先按 prefix 查（快），再 bcrypt compare（防 timing leak）。
   * 返回 null = 无效；返回 row = 有效（同时异步刷 lastUsedAt）。
   */
  async validate(rawToken: string, ip?: string): Promise<{
    id: string;
    userId: string;
    organizationId: string;
  } | null> {
    if (!rawToken.startsWith(TOKEN_PREFIX)) return null;
    const prefix = rawToken.slice(0, PREFIX_VISIBLE_LEN);
    const candidates = await this.prisma.aiUsageToken.findMany({
      where: { prefix, revokedAt: null },
      select: { id: true, userId: true, organizationId: true, tokenHash: true },
    });
    for (const c of candidates) {
      if (await bcrypt.compare(rawToken, c.tokenHash)) {
        // fire and forget; ignore failure
        this.prisma.aiUsageToken
          .update({
            where: { id: c.id },
            data: { lastUsedAt: new Date(), lastUsedIp: ip ?? null },
          })
          .catch(() => {});
        return { id: c.id, userId: c.userId, organizationId: c.organizationId };
      }
    }
    return null;
  }

  /**
   * Admin 视角：跨用户列出 token
   */
  async listAll(filter: {
    organizationId: string;
    userId?: string;
    status?: 'all' | 'active' | 'revoked';
    skip?: number;
    take?: number;
  }) {
    if (!filter.organizationId) {
      throw new ForbiddenException('AI_USAGE_ORG_CONTEXT_REQUIRED');
    }
    const where: any = { organizationId: filter.organizationId };
    if (filter.userId) where.userId = filter.userId;
    if (filter.status === 'active') where.revokedAt = null;
    if (filter.status === 'revoked') where.revokedAt = { not: null };
    const [items, total] = await Promise.all([
      this.prisma.aiUsageToken.findMany({
        where,
        orderBy: { createdAt: 'desc' },
        skip: filter.skip ?? 0,
        take: filter.take ?? 20,
        include: { user: { select: { id: true, displayName: true, email: true } } },
      }),
      this.prisma.aiUsageToken.count({ where }),
    ]);
    return { items, total };
  }
}
