import { v4 as uuidv4 } from "uuid";
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import { Inject } from "@nestjs/common";
import { IamRepository } from "./iam.repository.js";
import {
  ConflictError,
  UnauthorizedError,
  NotFoundError,
} from "../shared/errors/application-error.js";
import { env } from "../config/env.js";
import type { RegisterDto, LoginDto } from "./iam.dto.js";
import type { User, Role, Permission } from "./iam.schema.js";

export interface TokenPair {
  accessToken: string;
  refreshToken: string;
  expiresIn: number;
}

export interface UserInfo {
  id: string;
  email: string;
  name: string;
  roles: string[];
  permissions: string[];
  dataScope: string;
}

export interface ViewportItem {
  key: string;
  label: string;
  route: string;
  icon: string | null;
  sortOrder: string;
  requiredPermission: string | null;
}

// teacher 角色固定 ID（种子数据）
const TEACHER_ROLE_ID = "00000000-0000-0000-0000-000000000001";

export class IamService {
  constructor(
    @Inject(IamRepository) private readonly repository: IamRepository,
  ) {}

  async register(
    dto: RegisterDto,
  ): Promise<{ user: UserInfo; tokens: TokenPair }> {
    const existing = await this.repository.findUserByEmail(dto.email);
    if (existing) {
      throw new ConflictError("Email already registered");
    }

    const userId = uuidv4();
    const passwordHash = await bcrypt.hash(dto.password, 12);
    const user = await this.repository.createUser({
      id: userId,
      email: dto.email,
      passwordHash,
      name: dto.name,
    });

    // 默认分配 teacher 角色
    await this.repository.assignRole(userId, TEACHER_ROLE_ID);

    const { tokens } = await this.issueTokens(user);
    const info = await this.buildUserInfo(user);
    return { user: info, tokens };
  }

  async login(dto: LoginDto): Promise<{ user: UserInfo; tokens: TokenPair }> {
    const user = await this.repository.findUserByEmail(dto.email);
    if (!user) {
      throw new UnauthorizedError("Invalid credentials");
    }

    const valid = await bcrypt.compare(dto.password, user.passwordHash);
    if (!valid) {
      throw new UnauthorizedError("Invalid credentials");
    }

    if (user.status !== "active") {
      throw new UnauthorizedError("Account is not active");
    }

    const { tokens } = await this.issueTokens(user);
    const info = await this.buildUserInfo(user);
    return { user: info, tokens };
  }

  async refresh(refreshToken: string): Promise<TokenPair> {
    let payload: jwt.JwtPayload;
    try {
      const decoded = jwt.verify(refreshToken, env.JWT_SECRET);
      if (typeof decoded === "string") {
        throw new UnauthorizedError("Invalid refresh token");
      }
      payload = decoded;
    } catch {
      throw new UnauthorizedError("Invalid refresh token");
    }

    if (payload.type !== "refresh") {
      throw new UnauthorizedError("Invalid token type");
    }

    const sub = typeof payload.sub === "string" ? payload.sub : undefined;
    if (!sub) {
      throw new UnauthorizedError("Invalid token subject");
    }

    const user = await this.repository.findUserById(sub);
    if (!user) {
      throw new NotFoundError("User", sub);
    }

    return this.issueTokens(user).then((r) => r.tokens);
  }

  async getUserInfo(userId: string): Promise<UserInfo> {
    const user = await this.repository.findUserById(userId);
    if (!user) {
      throw new NotFoundError("User", userId);
    }
    return this.buildUserInfo(user);
  }

  // 有效权限聚合：多角色权限去重
  async getEffectivePermissions(userId: string): Promise<string[]> {
    const perms = await this.repository.getUserPermissions(userId);
    const unique = new Set(perms.map((p) => p.name));
    return [...unique];
  }

  async getUserViewports(userId: string): Promise<ViewportItem[]> {
    const viewports = await this.repository.getUserViewports(userId);
    const permissions = await this.getEffectivePermissions(userId);

    // 过滤：如果视口需要权限且用户不具备，则不返回
    return viewports
      .filter((vp) => {
        if (!vp.requiredPermission) return true;
        return permissions.includes(vp.requiredPermission);
      })
      .map((vp) => ({
        key: vp.viewportKey,
        label: vp.label,
        route: vp.route,
        icon: vp.icon,
        sortOrder: vp.sortOrder,
        requiredPermission: vp.requiredPermission,
      }))
      .sort((a, b) => a.sortOrder.localeCompare(b.sortOrder));
  }

  async getAllRoles(): Promise<Role[]> {
    return this.repository.getAllRoles();
  }

  async getAllPermissions(): Promise<Permission[]> {
    return this.repository.getAllPermissions();
  }

  private async issueTokens(user: User): Promise<{ tokens: TokenPair }> {
    const roles = await this.repository.getUserRoles(user.id);
    const roleNames = roles.map((r) => r.name);
    const dataScope = user.dataScope;

    const accessToken = jwt.sign(
      {
        sub: user.id,
        email: user.email,
        roles: roleNames,
        dataScope,
        type: "access",
      },
      env.JWT_SECRET,
      { issuer: env.JWT_ISSUER, audience: env.JWT_AUDIENCE, expiresIn: "15m" },
    );

    const refreshToken = jwt.sign(
      { sub: user.id, type: "refresh" },
      env.JWT_SECRET,
      { issuer: env.JWT_ISSUER, audience: env.JWT_AUDIENCE, expiresIn: "7d" },
    );

    // 存储 refresh token hash
    const tokenHash = await bcrypt.hash(refreshToken, 10);
    await this.repository.createRefreshToken({
      id: uuidv4(),
      userId: user.id,
      tokenHash,
      expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
    });

    return {
      tokens: {
        accessToken,
        refreshToken,
        expiresIn: 15 * 60,
      },
    };
  }

  private async buildUserInfo(user: User): Promise<UserInfo> {
    const roles = await this.repository.getUserRoles(user.id);
    const permissions = await this.repository.getUserPermissions(user.id);
    return {
      id: user.id,
      email: user.email,
      name: user.name,
      roles: roles.map((r) => r.name),
      permissions: permissions.map((p) => p.name),
      dataScope: user.dataScope,
    };
  }
}
