import {
  Injectable,
  NotFoundException,
  ConflictException,
  BadRequestException,
  UnauthorizedException,
  ForbiddenException,
} from '@nestjs/common';
import { User } from '@prisma/client';
import { PrismaService } from '../../prisma/prisma.service';
import * as bcrypt from 'bcrypt';

type UserWithRelations = User & {
  department: { id: string; name: string } | null;
  room: { id: string; name: string } | null;
};

export interface UserRecord {
  id: string;
  clubId: string;
  phone: string;
  role: string;
  firstName: string;
  lastName: string;
  isActive: boolean;
  createdAt: Date;
}

const ROLE_LABELS: Record<string, string> = {
  TRAINER: 'Тренер',
  COORDINATOR: 'Координатор',
  MANAGER: 'Фитнес-менеджер',
  RECEPTIONIST: 'Администратор',
  CLUB_ADMIN: 'Админ клуба',
  SUPER_ADMIN: 'Суперадмин',
};

@Injectable()
export class UsersService {
  constructor(private readonly prisma: PrismaService) {}

  async findAllByClub(clubId: string) {
    const users = await this.prisma.user.findMany({
      where: { clubId },
      include: { department: { select: { name: true } } },
      orderBy: [{ lastName: 'asc' }, { firstName: 'asc' }],
    });

    return {
      data: users.map((u) => ({
        id: u.id,
        number: u.number,
        name: [u.firstName, u.lastName].filter(Boolean).join(' '),
        role: ROLE_LABELS[u.role] ?? u.role,
        department: u.department?.name ?? '—',
        phone: u.phone,
        status: u.isActive ? 'active' : 'inactive',
        hiredAt: u.createdAt.toISOString().split('T')[0],
      })),
      hasMore: false,
      nextCursor: null,
    };
  }

  async findByNumber(clubId: string, number: number) {
    const user = await this.prisma.user.findFirst({
      where: { number, clubId },
      include: {
        department: { select: { id: true, name: true } },
        room: { select: { id: true, name: true } },
      },
    });

    if (!user) {
      throw new NotFoundException(`User with number ${number} not found`);
    }

    return this.mapUserDetail(user);
  }

  async findByIdDetailed(clubId: string, id: string) {
    const user = await this.prisma.user.findFirst({
      where: { id, clubId },
      include: {
        department: { select: { id: true, name: true } },
        room: { select: { id: true, name: true } },
      },
    });

    if (!user) {
      throw new NotFoundException(`User with id ${id} not found`);
    }

    return this.mapUserDetail(user);
  }

  private mapUserDetail(user: UserWithRelations) {
    return {
      id: user.id,
      number: user.number,
      clubId: user.clubId,
      phone: user.phone,
      role: user.role,
      roleLabel: ROLE_LABELS[user.role] ?? user.role,
      firstName: user.firstName,
      lastName: user.lastName,
      patronymic: user.patronymic,
      email: user.email,
      avatarUrl: user.avatarUrl,
      comment: user.comment,
      isActive: user.isActive,
      createdAt: user.createdAt.toISOString(),
      updatedAt: user.updatedAt.toISOString(),
      department: user.department ? { id: user.department.id, name: user.department.name } : null,
      room: user.room ? { id: user.room.id, name: user.room.name } : null,
    };
  }

  async createUser(
    clubId: string,
    dto: {
      name: string;
      phone: string;
      role: string;
      password: string;
      departmentId?: string;
    },
  ) {
    const existing = await this.prisma.user.findFirst({
      where: { phone: dto.phone, clubId },
    });
    if (existing) {
      throw new ConflictException('Пользователь с таким телефоном уже существует');
    }

    const [firstName, ...rest] = dto.name.trim().split(/\s+/);
    const lastName = rest.join(' ') || '';
    const passwordHash = await bcrypt.hash(dto.password, 10);

    // Get next sequential number for this club
    const maxResult = await this.prisma.user.aggregate({
      where: { clubId },
      _max: { number: true },
    });
    const nextNumber = (maxResult._max.number ?? 0) + 1;

    const user = await this.prisma.user.create({
      data: {
        clubId,
        phone: dto.phone,
        passwordHash,
        firstName,
        lastName,
        number: nextNumber,
        role: dto.role.toUpperCase() as
          | 'TRAINER'
          | 'COORDINATOR'
          | 'MANAGER'
          | 'RECEPTIONIST'
          | 'CLUB_ADMIN',
        departmentId: dto.departmentId || undefined,
      },
      include: { department: { select: { name: true } } },
    });

    return {
      id: user.id,
      number: user.number,
      name: [user.firstName, user.lastName].filter(Boolean).join(' '),
      role: ROLE_LABELS[user.role] ?? user.role,
      department: user.department?.name ?? '—',
      phone: user.phone,
      status: user.isActive ? 'active' : 'inactive',
      hiredAt: user.createdAt.toISOString().split('T')[0],
    };
  }

  async updateUser(
    id: string,
    dto: {
      name?: string;
      phone?: string;
      role?: string;
      departmentId?: string;
      email?: string;
      roomId?: string;
      comment?: string;
    },
  ) {
    await this.ensureExists(id);

    const data: Record<string, unknown> = {};
    if (dto.name) {
      const [firstName, ...rest] = dto.name.trim().split(/\s+/);
      data.firstName = firstName;
      data.lastName = rest.join(' ') || '';
    }
    if (dto.phone) data.phone = dto.phone;
    if (dto.role) data.role = dto.role.toUpperCase();
    if (dto.departmentId !== undefined) data.departmentId = dto.departmentId || null;
    if (dto.email !== undefined) data.email = dto.email || null;
    if (dto.roomId !== undefined) data.roomId = dto.roomId || null;
    if (dto.comment !== undefined) data.comment = dto.comment ?? null;

    const user = await this.prisma.user.update({
      where: { id },
      data,
      include: { department: { select: { name: true } } },
    });

    return {
      id: user.id,
      number: user.number,
      name: [user.firstName, user.lastName].filter(Boolean).join(' '),
      role: ROLE_LABELS[user.role] ?? user.role,
      department: user.department?.name ?? '—',
      phone: user.phone,
      status: user.isActive ? 'active' : 'inactive',
      hiredAt: user.createdAt.toISOString().split('T')[0],
    };
  }

  async changePassword(
    userId: string,
    requesterId: string,
    requesterRole: string,
    dto: { oldPassword?: string; newPassword: string },
  ): Promise<void> {
    const user = await this.prisma.user.findUnique({
      where: { id: userId },
      select: { id: true, passwordHash: true },
    });

    if (!user) {
      throw new NotFoundException(`User with id ${userId} not found`);
    }

    const isSelf = userId === requesterId;
    const isAdmin = ['super_admin', 'club_admin'].includes(requesterRole.toLowerCase());

    if (isSelf) {
      if (!dto.oldPassword) {
        throw new BadRequestException('Текущий пароль обязателен');
      }
      const isValid = await bcrypt.compare(dto.oldPassword, user.passwordHash);
      if (!isValid) {
        throw new UnauthorizedException('Неверный текущий пароль');
      }
    } else if (!isAdmin) {
      throw new ForbiddenException('Недостаточно прав для смены пароля');
    }

    const newHash = await bcrypt.hash(dto.newPassword, 10);
    await this.prisma.user.update({
      where: { id: userId },
      data: { passwordHash: newHash },
    });
  }

  async updateAvatar(id: string, avatarUrl: string): Promise<{ avatarUrl: string }> {
    await this.ensureExists(id);
    await this.prisma.user.update({
      where: { id },
      data: { avatarUrl },
    });
    return { avatarUrl };
  }

  async deleteUser(id: string) {
    await this.ensureExists(id);
    await this.prisma.user.update({
      where: { id },
      data: { isActive: false },
    });
  }

  async findById(id: string): Promise<UserRecord> {
    const users = await this.prisma.$queryRawUnsafe<UserRecord[]>(
      `SELECT id, "clubId", phone, role, "firstName", "lastName",
              "isActive", "createdAt"
       FROM users
       WHERE id = $1`,
      id,
    );

    if (!users.length) {
      throw new NotFoundException(`User with id ${id} not found`);
    }

    return users[0];
  }

  async findByPhone(phone: string, clubId: string): Promise<UserRecord | null> {
    const users = await this.prisma.$queryRawUnsafe<UserRecord[]>(
      `SELECT id, "clubId", phone, role, "firstName", "lastName",
              "isActive", "createdAt"
       FROM users
       WHERE phone = $1 AND "clubId" = $2`,
      phone,
      clubId,
    );

    return users.length ? users[0] : null;
  }

  async findStaff(clubId: string, search?: string) {
    const where: Record<string, any> = { clubId, isActive: true };
    if (search) {
      where.OR = [
        { firstName: { contains: search, mode: 'insensitive' } },
        { lastName: { contains: search, mode: 'insensitive' } },
      ];
    }
    const users = await this.prisma.user.findMany({
      where,
      select: { id: true, number: true, firstName: true, lastName: true, role: true },
      orderBy: [{ lastName: 'asc' }, { firstName: 'asc' }],
      take: 50,
    });
    return users;
  }

  async findTrainersByClub(clubId: string): Promise<UserRecord[]> {
    return this.prisma.$queryRawUnsafe<UserRecord[]>(
      `SELECT id, "clubId", phone, role, "firstName", "lastName",
              "isActive", "createdAt"
       FROM users
       WHERE "clubId" = $1 AND role = 'TRAINER' AND "isActive" = true
       ORDER BY "lastName", "firstName"`,
      clubId,
    );
  }

  private async ensureExists(id: string): Promise<void> {
    const count = await this.prisma.user.count({ where: { id } });
    if (count === 0) {
      throw new NotFoundException(`User with id ${id} not found`);
    }
  }
}
