import bcrypt from 'bcryptjs';
import { Repository, IsNull } from 'typeorm';
import { AppDataSource } from '../../config/typeorm.js';
import { User, UserStatus, Role } from '../auth/entities/index.js';
import { NotFoundError, ValidationError } from '../../shared/types/index.js';
import { logger } from '../../shared/utils/logger.js';
import { splitFullName, buildFullName } from '../auth/auth.service.js';

export interface CreateUserDto {
  tenant_id: string;
  email: string;
  password: string;
  full_name?: string;
  firstName?: string;
  lastName?: string;
  status?: UserStatus | 'active' | 'inactive' | 'pending';
  is_superuser?: boolean;
}

export interface UpdateUserDto {
  email?: string;
  full_name?: string;
  firstName?: string;
  lastName?: string;
  status?: UserStatus | 'active' | 'inactive' | 'pending' | 'suspended';
}

export interface UserListParams {
  page: number;
  limit: number;
  search?: string;
  status?: UserStatus | 'active' | 'inactive' | 'pending' | 'suspended';
  sortBy?: string;
  sortOrder?: 'asc' | 'desc';
}

export interface UserResponse {
  id: string;
  tenantId: string;
  email: string;
  fullName: string;
  firstName: string;
  lastName: string;
  avatarUrl: string | null;
  status: UserStatus;
  isSuperuser: boolean;
  emailVerifiedAt: Date | null;
  lastLoginAt: Date | null;
  lastLoginIp: string | null;
  loginCount: number;
  language: string;
  timezone: string;
  settings: Record<string, any>;
  createdAt: Date;
  updatedAt: Date | null;
  roles?: Role[];
}

/**
 * Transforma usuario de BD a formato frontend (con firstName/lastName)
 */
function transformUserResponse(user: User): UserResponse {
  const { passwordHash, ...rest } = user;
  const { firstName, lastName } = splitFullName(user.fullName || '');
  return {
    ...rest,
    firstName,
    lastName,
    roles: user.roles,
  };
}

export interface UsersListResult {
  users: UserResponse[];
  total: number;
}

class UsersService {
  private userRepository: Repository<User>;
  private roleRepository: Repository<Role>;

  constructor() {
    this.userRepository = AppDataSource.getRepository(User);
    this.roleRepository = AppDataSource.getRepository(Role);
  }

  async findAll(tenantId: string, params: UserListParams): Promise<UsersListResult> {
    const {
      page,
      limit,
      search,
      status,
      sortBy = 'createdAt',
      sortOrder = 'desc'
    } = params;

    const skip = (page - 1) * limit;

    // Mapa de campos para ordenamiento (frontend -> entity)
    const sortFieldMap: Record<string, string> = {
      createdAt: 'user.createdAt',
      email: 'user.email',
      fullName: 'user.fullName',
      status: 'user.status',
    };

    const orderField = sortFieldMap[sortBy] || 'user.createdAt';
    const orderDirection = sortOrder.toUpperCase() as 'ASC' | 'DESC';

    // Crear QueryBuilder
    const queryBuilder = this.userRepository
      .createQueryBuilder('user')
      .where('user.tenantId = :tenantId', { tenantId })
      .andWhere('user.deletedAt IS NULL');

    // Filtrar por búsqueda (email o fullName)
    if (search) {
      queryBuilder.andWhere(
        '(user.email ILIKE :search OR user.fullName ILIKE :search)',
        { search: `%${search}%` }
      );
    }

    // Filtrar por status
    if (status) {
      queryBuilder.andWhere('user.status = :status', { status });
    }

    // Obtener total y usuarios con paginación
    const [users, total] = await queryBuilder
      .orderBy(orderField, orderDirection)
      .skip(skip)
      .take(limit)
      .getManyAndCount();

    return {
      users: users.map(transformUserResponse),
      total,
    };
  }

  async findById(tenantId: string, userId: string): Promise<UserResponse> {
    const user = await this.userRepository.findOne({
      where: {
        id: userId,
        tenantId,
        deletedAt: IsNull(),
      },
      relations: ['roles'],
    });

    if (!user) {
      throw new NotFoundError('Usuario no encontrado');
    }

    return transformUserResponse(user);
  }

  async create(dto: CreateUserDto): Promise<UserResponse> {
    // Check if email already exists
    const existingUser = await this.userRepository.findOne({
      where: { email: dto.email.toLowerCase() },
    });

    if (existingUser) {
      throw new ValidationError('El email ya está registrado');
    }

    // Transformar firstName/lastName a fullName para almacenar en BD
    const fullName = buildFullName(dto.firstName, dto.lastName, dto.full_name);

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

    // Crear usuario con repository
    const user = this.userRepository.create({
      tenantId: dto.tenant_id,
      email: dto.email.toLowerCase(),
      passwordHash,
      fullName,
      status: dto.status as UserStatus || UserStatus.ACTIVE,
      isSuperuser: dto.is_superuser || false,
    });

    const savedUser = await this.userRepository.save(user);

    logger.info('User created', { userId: savedUser.id, email: savedUser.email });
    return transformUserResponse(savedUser);
  }

  async update(tenantId: string, userId: string, dto: UpdateUserDto): Promise<UserResponse> {
    // Obtener usuario existente
    const user = await this.userRepository.findOne({
      where: {
        id: userId,
        tenantId,
        deletedAt: IsNull(),
      },
    });

    if (!user) {
      throw new NotFoundError('Usuario no encontrado');
    }

    // Check email uniqueness if changing
    if (dto.email && dto.email.toLowerCase() !== user.email) {
      const emailExists = await this.userRepository.findOne({
        where: {
          email: dto.email.toLowerCase(),
        },
      });
      if (emailExists && emailExists.id !== userId) {
        throw new ValidationError('El email ya está en uso');
      }
    }

    // Actualizar campos
    if (dto.email !== undefined) {
      user.email = dto.email.toLowerCase();
    }

    // Soportar firstName/lastName o full_name
    const fullName = buildFullName(dto.firstName, dto.lastName, dto.full_name);
    if (fullName) {
      user.fullName = fullName;
    }

    if (dto.status !== undefined) {
      user.status = dto.status as UserStatus;
    }

    const updatedUser = await this.userRepository.save(user);

    logger.info('User updated', { userId: updatedUser.id });
    return transformUserResponse(updatedUser);
  }

  async delete(tenantId: string, userId: string, currentUserId?: string): Promise<void> {
    // Obtener usuario para soft delete
    const user = await this.userRepository.findOne({
      where: {
        id: userId,
        tenantId,
        deletedAt: IsNull(),
      },
    });

    if (!user) {
      throw new NotFoundError('Usuario no encontrado');
    }

    // Soft delete real con deletedAt y deletedBy
    user.deletedAt = new Date();
    if (currentUserId) {
      user.deletedBy = currentUserId;
    }
    await this.userRepository.save(user);

    logger.info('User deleted (soft)', { userId, deletedBy: currentUserId || 'unknown' });
  }

  async activate(tenantId: string, userId: string, currentUserId: string): Promise<UserResponse> {
    const user = await this.userRepository.findOne({
      where: {
        id: userId,
        tenantId,
        deletedAt: IsNull(),
      },
      relations: ['roles'],
    });

    if (!user) {
      throw new NotFoundError('Usuario no encontrado');
    }

    user.status = UserStatus.ACTIVE;
    user.updatedBy = currentUserId;
    const updatedUser = await this.userRepository.save(user);

    logger.info('User activated', { userId, activatedBy: currentUserId });
    return transformUserResponse(updatedUser);
  }

  async deactivate(tenantId: string, userId: string, currentUserId: string): Promise<UserResponse> {
    const user = await this.userRepository.findOne({
      where: {
        id: userId,
        tenantId,
        deletedAt: IsNull(),
      },
      relations: ['roles'],
    });

    if (!user) {
      throw new NotFoundError('Usuario no encontrado');
    }

    user.status = UserStatus.INACTIVE;
    user.updatedBy = currentUserId;
    const updatedUser = await this.userRepository.save(user);

    logger.info('User deactivated', { userId, deactivatedBy: currentUserId });
    return transformUserResponse(updatedUser);
  }

  async assignRole(userId: string, roleId: string): Promise<void> {
    // Obtener usuario con roles
    const user = await this.userRepository.findOne({
      where: { id: userId },
      relations: ['roles'],
    });

    if (!user) {
      throw new NotFoundError('Usuario no encontrado');
    }

    // Obtener rol
    const role = await this.roleRepository.findOne({
      where: { id: roleId },
    });

    if (!role) {
      throw new NotFoundError('Rol no encontrado');
    }

    // Verificar si ya tiene el rol
    const hasRole = user.roles?.some(r => r.id === roleId);
    if (!hasRole) {
      if (!user.roles) {
        user.roles = [];
      }
      user.roles.push(role);
      await this.userRepository.save(user);
    }

    logger.info('Role assigned to user', { userId, roleId });
  }

  async removeRole(userId: string, roleId: string): Promise<void> {
    // Obtener usuario con roles
    const user = await this.userRepository.findOne({
      where: { id: userId },
      relations: ['roles'],
    });

    if (!user) {
      throw new NotFoundError('Usuario no encontrado');
    }

    // Filtrar el rol a eliminar
    if (user.roles) {
      user.roles = user.roles.filter(r => r.id !== roleId);
      await this.userRepository.save(user);
    }

    logger.info('Role removed from user', { userId, roleId });
  }

  async getUserRoles(userId: string): Promise<Role[]> {
    const user = await this.userRepository.findOne({
      where: { id: userId },
      relations: ['roles'],
    });

    if (!user) {
      throw new NotFoundError('Usuario no encontrado');
    }

    return user.roles || [];
  }
}

export const usersService = new UsersService();
