import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { In, Like, Repository } from 'typeorm';
import { User } from './user.entity';
import { Role } from '../roles/role.entity';

export interface UserListQuery {
  search?: string;
  isActive?: boolean;
  page?: number;
  limit?: number;
}

/** Shape returned to clients - never includes the password hash. */
export interface PublicUser {
  id: number;
  email: string;
  displayName: string;
  isActive: boolean;
  roles: { id: number; name: string }[];
  createdAt: Date;
  updatedAt: Date;
}

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly usersRepo: Repository<User>,
    @InjectRepository(Role)
    private readonly rolesRepo: Repository<Role>,
  ) {}

  static toPublic(user: User): PublicUser {
    return {
      id: Number(user.id),
      email: user.email,
      displayName: user.displayName,
      isActive: !!user.isActive,
      roles: (user.roles || []).map((r) => ({ id: Number(r.id), name: r.name })),
      createdAt: user.createdAt,
      updatedAt: user.updatedAt,
    };
  }

  findByEmail(email: string): Promise<User | null> {
    return this.usersRepo.findOne({ where: { email } });
  }

  findByEmailOrId(id: number): Promise<User | null> {
    return this.usersRepo.findOne({ where: { id } });
  }

  findById(id: number): Promise<User | null> {
    return this.usersRepo.findOne({
      where: { id },
      relations: ['roles', 'roles.permissions'],
    });
  }

  async findAll(query: UserListQuery = {}) {
    const page = Math.max(1, Number(query.page) || 1);
    const limit = Math.min(200, Math.max(1, Number(query.limit) || 25));

    const where: Record<string, any>[] = [];
    if (query.search) {
      where.push({ email: Like(`%${query.search}%`) }, { displayName: Like(`%${query.search}%`) });
    }
    if (typeof query.isActive === 'boolean') {
      if (where.length === 0) {
        where.push({ isActive: query.isActive });
      } else {
        where.forEach((clause) => (clause.isActive = query.isActive));
      }
    }

    const [items, total] = await this.usersRepo.findAndCount({
      where: where.length ? where : undefined,
      relations: ['roles'],
      order: { createdAt: 'DESC' },
      skip: (page - 1) * limit,
      take: limit,
    });

    return {
      items: items.map(UsersService.toPublic),
      meta: { page, limit, total, totalPages: Math.ceil(total / limit) },
    };
  }

  async create(data: {
    email: string;
    passwordHash: string;
    displayName?: string;
    roleIds?: number[];
    isActive?: boolean;
  }): Promise<User> {
    const existing = await this.findByEmail(data.email);
    if (existing) throw new ConflictException('A user with that email already exists');

    const user = this.usersRepo.create({
      email: data.email,
      passwordHash: data.passwordHash,
      displayName: data.displayName ?? data.email,
      isActive: data.isActive ?? true,
    });

    if (data.roleIds?.length) {
      user.roles = await this.rolesRepo.findBy({ id: In(data.roleIds) });
    }

    return this.usersRepo.save(user);
  }

  async update(
    id: number,
    data: { displayName?: string; email?: string; isActive?: boolean },
  ): Promise<User> {
    const user = await this.usersRepo.findOne({ where: { id }, relations: ['roles'] });
    if (!user) throw new NotFoundException('User not found');

    if (data.email && data.email !== user.email) {
      const clash = await this.findByEmail(data.email);
      if (clash) throw new ConflictException('A user with that email already exists');
      user.email = data.email;
    }
    if (data.displayName !== undefined) user.displayName = data.displayName;
    if (data.isActive !== undefined) user.isActive = data.isActive;

    return this.usersRepo.save(user);
  }

  async setPassword(id: number, passwordHash: string): Promise<void> {
    const result = await this.usersRepo.update(id, { passwordHash });
    if (!result.affected) throw new NotFoundException('User not found');
  }

  async setActive(id: number, isActive: boolean): Promise<User> {
    return this.update(id, { isActive });
  }

  async assignRoles(userId: number, roleIds: number[]): Promise<User> {
    const user = await this.usersRepo.findOne({ where: { id: userId }, relations: ['roles'] });
    if (!user) throw new NotFoundException('User not found');

    user.roles = roleIds?.length ? await this.rolesRepo.findBy({ id: In(roleIds) }) : [];
    return this.usersRepo.save(user);
  }

  /** Soft-delete: accounts are deactivated so audit history stays resolvable. */
  async deactivate(id: number): Promise<boolean> {
    const result = await this.usersRepo.update(id, { isActive: false });
    return (result.affected || 0) > 0;
  }

  count(): Promise<number> {
    return this.usersRepo.count();
  }
}
