/**
 * UsersService - Gestión de usuarios
 *
 * CRUD de usuarios con soporte multi-tenant y RBAC.
 *
 * @module Users
 */

import { Repository, IsNull } from 'typeorm';
import * as bcrypt from 'bcryptjs';
import { User } from '../../core/entities/user.entity';
import { Role } from '../../auth/entities/role.entity';
import { UserRole } from '../../auth/entities/user-role.entity';

export interface CreateUserDto {
  email: string;
  password: string;
  firstName: string;
  lastName: string;
  tenantId: string;
  roles?: string[];
}

export interface UpdateUserDto {
  firstName?: string;
  lastName?: string;
  isActive?: boolean;
}

export interface AssignRoleDto {
  userId: string;
  roleCode: string;
  tenantId: string;
}

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

export class UsersService {
  constructor(
    private readonly userRepository: Repository<User>,
    private readonly roleRepository: Repository<Role>,
    private readonly userRoleRepository: Repository<UserRole>
  ) {}

  /**
   * Listar usuarios de un tenant
   */
  async findAll(options: UserListOptions): Promise<{ users: User[]; total: number }> {
    const { tenantId, page = 1, limit = 20, search, isActive } = options;

    const query = this.userRepository
      .createQueryBuilder('user')
      .leftJoinAndSelect('user.tenant', 'tenant')
      .where('user.tenant_id = :tenantId', { tenantId })
      .andWhere('user.deleted_at IS NULL');

    if (search) {
      query.andWhere(
        '(user.email ILIKE :search OR user.first_name ILIKE :search OR user.last_name ILIKE :search)',
        { search: `%${search}%` }
      );
    }

    if (isActive !== undefined) {
      query.andWhere('user.is_active = :isActive', { isActive });
    }

    const total = await query.getCount();
    const users = await query
      .skip((page - 1) * limit)
      .take(limit)
      .orderBy('user.created_at', 'DESC')
      .getMany();

    return { users, total };
  }

  /**
   * Obtener usuario por ID
   */
  async findById(id: string, tenantId: string): Promise<User | null> {
    return this.userRepository.findOne({
      where: { id, tenantId, deletedAt: IsNull() } as any,
      relations: ['tenant'],
    });
  }

  /**
   * Obtener usuario por email
   */
  async findByEmail(email: string, tenantId: string): Promise<User | null> {
    return this.userRepository.findOne({
      where: { email, tenantId, deletedAt: IsNull() } as any,
    });
  }

  /**
   * Crear usuario
   */
  async create(dto: CreateUserDto, createdBy?: string): Promise<User> {
    // Verificar email único en tenant
    const existing = await this.findByEmail(dto.email, dto.tenantId);
    if (existing) {
      throw new Error('Email already exists in this tenant');
    }

    const passwordHash = await bcrypt.hash(dto.password, 12);

    const user = await this.userRepository.save(
      this.userRepository.create({
        email: dto.email,
        passwordHash,
        firstName: dto.firstName,
        lastName: dto.lastName,
        tenantId: dto.tenantId,
        defaultTenantId: dto.tenantId,
        isActive: true,
        roles: dto.roles || ['viewer'],
      })
    );

    // Asignar roles si se especificaron
    if (dto.roles && dto.roles.length > 0) {
      for (const roleCode of dto.roles) {
        await this.assignRole({ userId: user.id, roleCode, tenantId: dto.tenantId }, createdBy);
      }
    }

    return user;
  }

  /**
   * Actualizar usuario
   */
  async update(id: string, tenantId: string, dto: UpdateUserDto): Promise<User> {
    const user = await this.findById(id, tenantId);
    if (!user) {
      throw new Error('User not found');
    }

    await this.userRepository.update(id, {
      ...dto,
      updatedAt: new Date(),
    });

    return this.findById(id, tenantId) as Promise<User>;
  }

  /**
   * Soft delete de usuario
   */
  async delete(id: string, tenantId: string, _deletedBy?: string): Promise<void> {
    const user = await this.findById(id, tenantId);
    if (!user) {
      throw new Error('User not found');
    }

    await this.userRepository.update(id, {
      deletedAt: new Date(),
      isActive: false,
    });
  }

  /**
   * Activar/Desactivar usuario
   */
  async setActive(id: string, tenantId: string, isActive: boolean): Promise<User> {
    const user = await this.findById(id, tenantId);
    if (!user) {
      throw new Error('User not found');
    }

    await this.userRepository.update(id, { isActive });
    return this.findById(id, tenantId) as Promise<User>;
  }

  /**
   * Asignar rol a usuario
   */
  async assignRole(dto: AssignRoleDto, assignedBy?: string): Promise<UserRole> {
    const role = await this.roleRepository.findOne({
      where: { code: dto.roleCode, isActive: true },
    });

    if (!role) {
      throw new Error('Role not found');
    }

    // Verificar si ya tiene el rol
    const existing = await this.userRoleRepository.findOne({
      where: { userId: dto.userId, roleId: role.id, tenantId: dto.tenantId },
    });

    if (existing) {
      return existing;
    }

    return this.userRoleRepository.save(
      this.userRoleRepository.create({
        userId: dto.userId,
        roleId: role.id,
        tenantId: dto.tenantId,
        assignedBy,
      })
    );
  }

  /**
   * Remover rol de usuario
   */
  async removeRole(userId: string, roleCode: string, tenantId: string): Promise<void> {
    const role = await this.roleRepository.findOne({
      where: { code: roleCode },
    });

    if (!role) {
      throw new Error('Role not found');
    }

    await this.userRoleRepository.delete({
      userId,
      roleId: role.id,
      tenantId,
    });
  }

  /**
   * Obtener roles de usuario
   */
  async getUserRoles(userId: string, tenantId: string): Promise<Role[]> {
    const userRoles = await this.userRoleRepository.find({
      where: { userId, tenantId },
      relations: ['role'],
    });

    return userRoles.map((ur) => ur.role);
  }

  /**
   * Listar todos los roles disponibles
   */
  async listRoles(): Promise<Role[]> {
    return this.roleRepository.find({
      where: { isActive: true },
      order: { name: 'ASC' },
    });
  }
}
