import {
  Injectable,
  ConflictException,
  NotFoundException,
  BadRequestException,
  ForbiddenException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ClsService } from 'nestjs-cls';
import * as bcrypt from 'bcrypt';
import { UserRepository } from './repositories/user.repository';
import { RoleEntity } from '../../entities/role.entity';
import { CreateUserDto, UpdateUserDto, ResetPasswordDto } from './dto';
import { CLS_USER_ID } from '../../common/cls/cls.constants';
import { serviceMessages } from '../../common/constants/messages';

const MSG = serviceMessages('User');

@Injectable()
export class UserService {
  constructor(
    private readonly userRepo: UserRepository,
    @InjectRepository(RoleEntity)
    private readonly roleRepo: Repository<RoleEntity>,
    private readonly cls: ClsService,
  ) {}

  /**
   * Get current user ID from CLS and throw if operating on self.
   */
  private preventSelfAction(targetUserId: string, action: string): void {
    const currentUserId = this.cls.get<string>(CLS_USER_ID);
    if (currentUserId === targetUserId) {
      throw new ForbiddenException(`You cannot ${action} your own account`);
    }
  }

  async findAll(query?: { page?: number; limit?: number; search?: string }) {
    const result = await this.userRepo.findPaginated({
      page: query?.page,
      limit: query?.limit,
      search: query?.search,
      searchColumns: ['name', 'email', 'phone'],
      relations: ['role'],
      order: { name: 'ASC' },
    });
    return {
      items: result.items.map((u) => this.toListResponse(u)),
      meta: result.meta,
    };
  }

  async findById(id: string) {
    const user = await this.userRepo.findByIdWithRole(id);
    if (!user) throw new NotFoundException(MSG.NOT_FOUND);
    return this.toDetailResponse(user);
  }

  async create(dto: CreateUserDto) {
    const existing = await this.userRepo.findByEmail(dto.email);
    if (existing) throw new ConflictException(MSG.ALREADY_EXISTS('email'));

    const role = await this.roleRepo.findOneBy({ id: dto.role_id });
    if (!role) throw new BadRequestException('Invalid role ID');

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

    const user = await this.userRepo.save({
      name: dto.name,
      email: dto.email,
      phone: dto.phone || null,
      password_hash: passwordHash,
      role_id: dto.role_id,
      is_active: dto.is_active !== undefined ? dto.is_active : true,
    });

    return { id: user.id, message: MSG.CREATED };
  }

  async update(id: string, dto: UpdateUserDto) {
    // Prevent admin from changing their own role
    if (dto.role_id) {
      this.preventSelfAction(id, 'change your own role');
    }

    const user = await this.userRepo.findByIdWithRole(id);
    if (!user) throw new NotFoundException(MSG.NOT_FOUND);

    if (dto.email && dto.email !== user.email) {
      const existing = await this.userRepo.findByEmail(dto.email);
      if (existing) throw new ConflictException(MSG.ALREADY_EXISTS('email'));
    }

    if (dto.role_id) {
      const role = await this.roleRepo.findOneBy({ id: dto.role_id });
      if (!role) throw new BadRequestException('Invalid role ID');
    }

    const updateData: any = {};
    if (dto.name !== undefined) updateData.name = dto.name;
    if (dto.email !== undefined) updateData.email = dto.email;
    if (dto.phone !== undefined) updateData.phone = dto.phone;
    if (dto.role_id !== undefined) updateData.role_id = dto.role_id;
    if (dto.is_active !== undefined) updateData.is_active = dto.is_active;

    await this.userRepo.update(id, updateData);
    return { id, message: MSG.UPDATED };
  }

  async resetPassword(id: string, dto: ResetPasswordDto) {
    const user = await this.userRepo.findById(id);
    if (!user) throw new NotFoundException(MSG.NOT_FOUND);

    const passwordHash = await bcrypt.hash(dto.new_password, 10);
    await this.userRepo.update(id, { password_hash: passwordHash } as any);
    return { message: 'Password reset successfully' };
  }

  async toggleStatus(id: string) {
    this.preventSelfAction(id, 'deactivate yourself');

    const user = await this.userRepo.findById(id);
    if (!user) throw new NotFoundException(MSG.NOT_FOUND);

    await this.userRepo.update(id, { is_active: !user.is_active } as any);
    return {
      id,
      is_active: !user.is_active,
      message: `User ${!user.is_active ? 'activated' : 'deactivated'} successfully`,
    };
  }

  async remove(id: string) {
    this.preventSelfAction(id, 'delete yourself');

    const user = await this.userRepo.findById(id);
    if (!user) throw new NotFoundException(MSG.NOT_FOUND);

    await this.userRepo.softDelete(id);
    return { message: MSG.DELETED };
  }

  private toListResponse(user: any) {
    return {
      id: user.id,
      name: user.name,
      email: user.email,
      phone: user.phone,
      role: user.role ? { id: user.role.id, name: user.role.name } : null,
      is_active: user.is_active,
      last_login_at: user.last_login_at,
      created_at: user.created_at,
      updated_at: user.updated_at,
    };
  }

  private toDetailResponse(user: any) {
    return {
      id: user.id,
      name: user.name,
      email: user.email,
      phone: user.phone,
      is_active: user.is_active,
      last_login_at: user.last_login_at,
      created_at: user.created_at,
      updated_at: user.updated_at,
      created_by: user.created_by,
      updated_by: user.updated_by,
      role: user.role ? {
        id: user.role.id,
        name: user.role.name,
        description: user.role.description,
        is_system: user.role.is_system,
        permissions: user.role.permissions?.map((p: any) => ({
          module: p.module,
          access_level: p.access_level,
        })) || [],
      } : null,
    };
  }
}
