import { Injectable } from '@nestjs/common';
import { and, asc, count, eq, ne } from 'drizzle-orm';
import type { ModuleAccessMap } from '../../common/access/modules';
import { DatabaseService } from '../../common/database/database.service';
import { appState, users } from '../../database/schema';

export interface ManagedUser {
  id: string;
  email: string;
  displayName: string | null;
  role: 'USER' | 'ADMIN';
  isActive: boolean;
  createdAt: Date;
  updatedAt: Date;
}

/** A managed user together with the entitlements shown in the admin cockpit. */
export interface ManagedUserWithAccess extends ManagedUser {
  modules: ModuleAccessMap;
}

export interface NewManagedUser {
  email: string;
  passwordHash: string;
  displayName: string | null;
  role: 'USER' | 'ADMIN';
}

export interface ManagedUserPatch {
  displayName?: string | null;
  role?: 'USER' | 'ADMIN';
  isActive?: boolean;
  passwordHash?: string;
}

const MANAGED_USER_COLUMNS = {
  id: users.id,
  email: users.email,
  displayName: users.displayName,
  role: users.role,
  isActive: users.isActive,
  createdAt: users.createdAt,
  updatedAt: users.updatedAt,
} as const;

@Injectable()
export class UsersRepository {
  constructor(private readonly database: DatabaseService) {}

  list(): Promise<ManagedUser[]> {
    return this.database.db
      .select(MANAGED_USER_COLUMNS)
      .from(users)
      .orderBy(asc(users.createdAt));
  }

  async findById(id: string): Promise<ManagedUser | null> {
    const [row] = await this.database.db
      .select(MANAGED_USER_COLUMNS)
      .from(users)
      .where(eq(users.id, id))
      .limit(1);
    return row ?? null;
  }

  async findByEmail(email: string): Promise<ManagedUser | null> {
    const [row] = await this.database.db
      .select(MANAGED_USER_COLUMNS)
      .from(users)
      .where(eq(users.email, email))
      .limit(1);
    return row ?? null;
  }

  async create(input: NewManagedUser): Promise<ManagedUser> {
    const [row] = await this.database.db
      .insert(users)
      .values(input)
      .returning(MANAGED_USER_COLUMNS);
    return row;
  }

  async update(id: string, patch: ManagedUserPatch): Promise<ManagedUser> {
    const [row] = await this.database.db
      .update(users)
      .set({ ...patch, updatedAt: new Date() })
      .where(eq(users.id, id))
      .returning(MANAGED_USER_COLUMNS);
    return row;
  }

  /**
   * Number of active administrators other than `excludedId`. Used to refuse the
   * change that would leave the installation with no way in.
   */
  async countOtherActiveAdmins(excludedId: string): Promise<number> {
    const [row] = await this.database.db
      .select({ value: count() })
      .from(users)
      .where(and(eq(users.role, 'ADMIN'), eq(users.isActive, true), ne(users.id, excludedId)));
    return Number(row?.value ?? 0);
  }

  /** How many state blobs a user owns — shown before a destructive change. */
  async countStateRows(userId: string): Promise<number> {
    const [row] = await this.database.db
      .select({ value: count() })
      .from(appState)
      .where(eq(appState.ownerId, userId));
    return Number(row?.value ?? 0);
  }
}
