import { eq, inArray, and } from "drizzle-orm";
import { getDb } from "../config/database.js";
import {
  users,
  roles,
  userRoles,
  permissions,
  rolePermissions,
  refreshTokens,
  roleViewports,
  studentGuardians,
  userAuditLog,
  passwordHistory,
} from "./iam.schema.js";
import type {
  User,
  Role,
  Permission,
  RoleViewport,
  StudentGuardian,
  AuditLog,
  DataScope,
} from "./iam.schema.js";
import { DatabaseError } from "../shared/errors/application-error.js";

/**
 * IAM 数据访问层。
 *
 * 覆盖：用户 CRUD、角色/权限查询、刷新令牌管理、视口查询、
 * 学生-家长关系、审计日志、密码历史。
 */
export class IamRepository {
  // ============ 用户 ============

  async createUser(data: {
    id: string;
    email: string;
    passwordHash: string;
    name: string;
    dataScope?: DataScope;
  }): Promise<User> {
    const db = getDb();
    await db.insert(users).values(data);
    const [result] = await db.select().from(users).where(eq(users.id, data.id));
    if (!result) {
      throw new DatabaseError("Failed to create user");
    }
    return result;
  }

  async findUserByEmail(email: string): Promise<User | undefined> {
    const db = getDb();
    const [result] = await db
      .select()
      .from(users)
      .where(eq(users.email, email));
    return result;
  }

  async findUserById(id: string): Promise<User | undefined> {
    const db = getDb();
    const [result] = await db.select().from(users).where(eq(users.id, id));
    return result;
  }

  async batchFindUsers(ids: string[]): Promise<User[]> {
    if (ids.length === 0) return [];
    const db = getDb();
    return db.select().from(users).where(inArray(users.id, ids));
  }

  async updateUserStatus(userId: string, status: string): Promise<void> {
    const db = getDb();
    await db.update(users).set({ status }).where(eq(users.id, userId));
  }

  async updatePassword(userId: string, passwordHash: string): Promise<void> {
    const db = getDb();
    await db
      .update(users)
      .set({ passwordHash, passwordChangedAt: new Date() })
      .where(eq(users.id, userId));
  }

  // ============ 角色 ============

  async getUserRoles(userId: string): Promise<Role[]> {
    const db = getDb();
    const result = await db
      .select()
      .from(roles)
      .innerJoin(userRoles, eq(roles.id, userRoles.roleId))
      .where(eq(userRoles.userId, userId));
    return result.map((r) => r.iam_roles);
  }

  async getAllRoles(): Promise<Role[]> {
    const db = getDb();
    return db.select().from(roles);
  }

  async findRoleByName(name: string): Promise<Role | undefined> {
    const db = getDb();
    const [result] = await db.select().from(roles).where(eq(roles.name, name));
    return result;
  }

  async assignRole(userId: string, roleId: string): Promise<void> {
    const db = getDb();
    await db.insert(userRoles).values({ userId, roleId });
  }

  async revokeRole(userId: string, roleId: string): Promise<void> {
    const db = getDb();
    await db
      .delete(userRoles)
      .where(and(eq(userRoles.userId, userId), eq(userRoles.roleId, roleId)));
  }

  // ============ 权限 ============

  async getUserPermissions(userId: string): Promise<Permission[]> {
    const db = getDb();
    const userRoleRows = await db
      .select()
      .from(userRoles)
      .where(eq(userRoles.userId, userId));
    const roleIds = userRoleRows.map((r) => r.roleId);
    if (roleIds.length === 0) return [];
    const result = await db
      .select()
      .from(permissions)
      .innerJoin(
        rolePermissions,
        eq(permissions.id, rolePermissions.permissionId),
      )
      .where(inArray(rolePermissions.roleId, roleIds));
    return result.map((r) => r.iam_permissions);
  }

  async getAllPermissions(): Promise<Permission[]> {
    const db = getDb();
    return db.select().from(permissions);
  }

  // ============ 视口 ============

  async getUserViewports(userId: string): Promise<RoleViewport[]> {
    const db = getDb();
    const userRoleRows = await db
      .select()
      .from(userRoles)
      .where(eq(userRoles.userId, userId));
    const roleIds = userRoleRows.map((r) => r.roleId);
    if (roleIds.length === 0) return [];
    return db
      .select()
      .from(roleViewports)
      .where(inArray(roleViewports.roleId, roleIds));
  }

  // ============ 刷新令牌 ============

  async createRefreshToken(data: {
    id: string;
    userId: string;
    tokenHash: string;
    jti?: string;
    expiresAt: Date;
  }): Promise<void> {
    const db = getDb();
    await db.insert(refreshTokens).values(data);
  }

  async findRefreshTokenByHash(tokenHash: string): Promise<
    | {
        id: string;
        userId: string;
        jti: string | null;
        expiresAt: Date;
        revokedAt: Date | null;
      }
    | undefined
  > {
    const db = getDb();
    const [result] = await db
      .select({
        id: refreshTokens.id,
        userId: refreshTokens.userId,
        jti: refreshTokens.jti,
        expiresAt: refreshTokens.expiresAt,
        revokedAt: refreshTokens.revokedAt,
      })
      .from(refreshTokens)
      .where(eq(refreshTokens.tokenHash, tokenHash));
    return result;
  }

  async revokeRefreshToken(id: string): Promise<void> {
    const db = getDb();
    await db
      .update(refreshTokens)
      .set({ revokedAt: new Date() })
      .where(eq(refreshTokens.id, id));
  }

  async revokeAllUserTokens(userId: string): Promise<void> {
    const db = getDb();
    await db
      .update(refreshTokens)
      .set({ revokedAt: new Date() })
      .where(eq(refreshTokens.userId, userId));
  }

  // ============ 学生-家长关系（I6 裁决） ============

  async getChildrenByGuardian(guardianId: string): Promise<
    Array<{
      studentId: string;
      studentName: string;
      relation: string;
    }>
  > {
    const db = getDb();
    const result = await db
      .select({
        studentId: studentGuardians.studentId,
        studentName: users.name,
        relation: studentGuardians.relation,
      })
      .from(studentGuardians)
      .innerJoin(users, eq(studentGuardians.studentId, users.id))
      .where(eq(studentGuardians.guardianId, guardianId));
    return result;
  }

  async createStudentGuardianRelation(data: {
    id: string;
    studentId: string;
    guardianId: string;
    relation: string;
  }): Promise<StudentGuardian> {
    const db = getDb();
    await db.insert(studentGuardians).values(data);
    const [result] = await db
      .select()
      .from(studentGuardians)
      .where(eq(studentGuardians.id, data.id));
    if (!result) {
      throw new DatabaseError("Failed to create student-guardian relation");
    }
    return result;
  }

  // ============ 审计日志（president §5.5） ============

  async createAuditLog(data: {
    id: string;
    actorUserId: string;
    action: string;
    resourceType: string;
    resourceId: string;
    beforeState?: string | null;
    afterState?: string | null;
    ip?: string | null;
    userAgent?: string | null;
    traceId?: string | null;
  }): Promise<void> {
    const db = getDb();
    await db.insert(userAuditLog).values(data);
  }

  async queryAuditLog(options: {
    actorUserId?: string;
    resourceType?: string;
    resourceId?: string;
    limit?: number;
    offset?: number;
  }): Promise<AuditLog[]> {
    const db = getDb();
    let query = db.select().from(userAuditLog).$dynamic();

    if (options.actorUserId) {
      query = query.where(eq(userAuditLog.actorUserId, options.actorUserId));
    }
    if (options.resourceType) {
      query = query.where(eq(userAuditLog.resourceType, options.resourceType));
    }
    if (options.resourceId) {
      query = query.where(eq(userAuditLog.resourceId, options.resourceId));
    }

    const limit = options.limit ?? 50;
    const offset = options.offset ?? 0;
    return query.limit(limit).offset(offset);
  }

  // ============ 密码历史 ============

  async getPasswordHistory(userId: string, limit = 5): Promise<string[]> {
    const db = getDb();
    const rows = await db
      .select({ passwordHash: passwordHistory.passwordHash })
      .from(passwordHistory)
      .where(eq(passwordHistory.userId, userId))
      .limit(limit);
    return rows.map((r) => r.passwordHash);
  }

  async addPasswordHistory(
    userId: string,
    passwordHash: string,
  ): Promise<void> {
    const db = getDb();
    const id = crypto.randomUUID();
    await db.insert(passwordHistory).values({ id, userId, passwordHash });
  }
}
