import { Injectable } from '@nestjs/common';
import * as bcrypt from 'bcryptjs';
import { randomBytes } from 'crypto';
import { PrismaService } from '../../shared/prisma/prisma.service';
import { appBadRequest, appNotFound } from '../../shared/common/app-error';
import { ensureUserInviteCode } from '../../shared/common/invite-code.util';

const STAFF_ROLE_CODES = ['SUPER_ADMIN', 'MATCH_ADMIN', 'FINANCE_ADMIN', 'SUPPORT'] as const;

function generatePassword(length = 10): string {
  const raw = randomBytes(12).toString('base64url').replace(/[^a-zA-Z0-9]/g, '');
  const base = (raw + 'Aa1').slice(0, Math.max(8, length));
  return base;
}

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

  async listRoles() {
    const roles = await this.prisma.role.findMany({
      where: { code: { in: [...STAFF_ROLE_CODES] } },
      orderBy: { code: 'asc' },
      select: { id: true, code: true, name: true },
    });
    return roles.map((r) => ({
      id: r.id.toString(),
      code: r.code,
      name: r.name,
    }));
  }

  async listStaff(page = 1, pageSize = 20, keyword?: string) {
    const where = {
      userType: 'ADMIN' as const,
      deletedAt: null,
      ...(keyword?.trim()
        ? { username: { contains: keyword.trim(), mode: 'insensitive' as const } }
        : {}),
    };
    const [total, items] = await Promise.all([
      this.prisma.user.count({ where }),
      this.prisma.user.findMany({
        where,
        skip: (page - 1) * pageSize,
        take: pageSize,
        orderBy: { createdAt: 'desc' },
        include: {
          auth: { select: { lastLoginAt: true } },
          adminRole: { include: { role: { select: { code: true, name: true } } } },
        },
      }),
    ]);
    return {
      total,
      items: items.map((u) => ({
        id: u.id.toString(),
        username: u.username,
        status: u.status,
        role: u.adminRole?.role?.code ?? null,
        roleName: u.adminRole?.role?.name ?? null,
        lastLoginAt: u.auth?.lastLoginAt ?? null,
        createdAt: u.createdAt,
        visibleMenus: u.visibleMenus,
      })),
    };
  }

  async createStaff(data: { username: string; password: string; roleCode: string; visibleMenus?: string }) {
    const username = data.username.trim();
    if (!username) throw appBadRequest('USERNAME_REQUIRED');
    if (data.password.length < 8) throw appBadRequest('PASSWORD_MIN_LENGTH');
    if (!STAFF_ROLE_CODES.includes(data.roleCode as (typeof STAFF_ROLE_CODES)[number])) {
      throw appBadRequest('INVALID_ROLE');
    }

    const existing = await this.prisma.user.findUnique({ where: { username } });
    if (existing) throw appBadRequest('USERNAME_TAKEN');

    const role = await this.prisma.role.findUnique({ where: { code: data.roleCode } });
    if (!role) throw appBadRequest('INVALID_ROLE');

    const hash = await bcrypt.hash(data.password, 10);
    const user = await this.prisma.user.create({
      data: {
        username,
        userType: 'ADMIN',
        auth: { create: { passwordHash: hash } },
        adminRole: { create: { roleId: role.id } },
        visibleMenus: data.visibleMenus,
      },
      include: {
        adminRole: { include: { role: { select: { code: true, name: true } } } },
      },
    });
    await ensureUserInviteCode(this.prisma, user.id);

    return {
      id: user.id.toString(),
      username: user.username,
      status: user.status,
      role: user.adminRole?.role?.code ?? null,
      roleName: user.adminRole?.role?.name ?? null,
      visibleMenus: user.visibleMenus,
    };
  }

  async updateStaff(
    staffId: bigint,
    data: { status?: string; roleCode?: string; password?: string; visibleMenus?: string },
  ) {
    const user = await this.prisma.user.findFirst({
      where: { id: staffId, userType: 'ADMIN', deletedAt: null },
      include: { auth: true, adminRole: true },
    });
    if (!user) throw appNotFound('USER_NOT_FOUND');

    if (data.status !== undefined) {
      if (!['ACTIVE', 'SUSPENDED', 'DISABLED'].includes(data.status)) {
        throw appBadRequest('INVALID_STATUS');
      }
      await this.prisma.user.update({
        where: { id: staffId },
        data: { status: data.status },
      });
    }

    if (data.roleCode !== undefined) {
      if (!STAFF_ROLE_CODES.includes(data.roleCode as (typeof STAFF_ROLE_CODES)[number])) {
        throw appBadRequest('INVALID_ROLE');
      }
      const role = await this.prisma.role.findUnique({ where: { code: data.roleCode } });
      if (!role) throw appBadRequest('INVALID_ROLE');
      if (user.adminRole) {
        await this.prisma.adminUserRole.update({
          where: { userId: staffId },
          data: { roleId: role.id },
        });
      } else {
        await this.prisma.adminUserRole.create({
          data: { userId: staffId, roleId: role.id },
        });
      }
    }

    if (data.visibleMenus !== undefined) {
      await this.prisma.user.update({
        where: { id: staffId },
        data: { visibleMenus: data.visibleMenus },
      });
    }

    let plainPassword: string | undefined;
    if (data.password !== undefined) {
      if (data.password.length < 8) throw appBadRequest('PASSWORD_MIN_LENGTH');
      if (!user.auth) throw appBadRequest('AUTH_INFO_MISSING');
      plainPassword = data.password;
      const hash = await bcrypt.hash(data.password, 10);
      await this.prisma.userAuth.update({
        where: { userId: staffId },
        data: { passwordHash: hash, loginFailCount: 0, lockedUntil: null },
      });
    }

    const refreshed = await this.prisma.user.findUnique({
      where: { id: staffId },
      include: { adminRole: { include: { role: { select: { code: true, name: true } } } } },
    });

    return {
      id: refreshed!.id.toString(),
      username: refreshed!.username,
      status: refreshed!.status,
      role: refreshed!.adminRole?.role?.code ?? null,
      roleName: refreshed!.adminRole?.role?.name ?? null,
      visibleMenus: refreshed!.visibleMenus,
      ...(plainPassword ? { password: plainPassword } : {}),
    };
  }

  async deleteStaff(staffId: bigint, operatorId?: bigint) {
    if (operatorId && staffId === operatorId) {
      throw appBadRequest('CANNOT_DELETE_SELF');
    }
    const user = await this.prisma.user.findFirst({
      where: { id: staffId, userType: 'ADMIN', deletedAt: null },
      include: { adminRole: { include: { role: true } } },
    });
    if (!user) throw appNotFound('STAFF_NOT_FOUND');

    if (user.adminRole?.role?.code === 'SUPER_ADMIN') {
      const superAdminCount = await this.prisma.user.count({
        where: {
          userType: 'ADMIN',
          deletedAt: null,
          adminRole: { role: { code: 'SUPER_ADMIN' } },
        },
      });
      if (superAdminCount <= 1) {
        throw appBadRequest('CANNOT_DELETE_LAST_SUPER_ADMIN');
      }
    }

    return this.prisma.user.update({
      where: { id: staffId },
      data: { deletedAt: new Date(), status: 'DISABLED' },
    });
  }

  async resetPlayerPassword(playerId: bigint, password?: string) {
    const user = await this.prisma.user.findFirst({
      where: { id: playerId, userType: 'PLAYER', deletedAt: null },
      include: { auth: true },
    });
    if (!user) throw appNotFound('PLAYER_NOT_FOUND');
    if (!user.auth) throw appBadRequest('AUTH_INFO_MISSING');

    const nextPassword = password?.trim() || generatePassword();
    if (nextPassword.length < 8) throw appBadRequest('PASSWORD_MIN_LENGTH');

    const hash = await bcrypt.hash(nextPassword, 10);
    await this.prisma.userAuth.update({
      where: { userId: playerId },
      data: { passwordHash: hash, loginFailCount: 0, lockedUntil: null },
    });
    await this.prisma.userPreference.upsert({
      where: { userId: playerId },
      create: { userId: playerId, managedPassword: nextPassword },
      update: { managedPassword: nextPassword },
    });

    return { password: nextPassword };
  }
}
