import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
import * as bcrypt from 'bcryptjs';
import { SUPPORTED_LOCALES, isValidAvatarKey, assertPlayerUsername } from '@thebet365/shared';
import { PrismaService } from '../../shared/prisma/prisma.service';
import { SystemConfigService } from '../../shared/config/system-config.service';
import { AgentsService } from '../agent/agents.service';

export type PlayerListFilters = {
  keyword?: string;
  parentId?: bigint;
  platformDirect?: boolean;
  status?: string;
};

@Injectable()
export class UsersService {
  constructor(
    private prisma: PrismaService,
    private agents: AgentsService,
    private systemConfig: SystemConfigService,
  ) {}

  private buildAffiliationAgents(
    parent?: {
      username: string;
      agentLevel: number | null;
      parent?: { username: string; agentLevel: number | null } | null;
    } | null,
  ): string[] {
    if (!parent) return [];
    if (parent.agentLevel === 2 && parent.parent?.username) {
      return [parent.parent.username, parent.username];
    }
    return [parent.username];
  }

  private formatPlayerRow(
    u: {
      id: bigint;
      username: string;
      status: string;
      locale: string;
      parentId: bigint | null;
      createdAt: Date;
      updatedAt: Date;
      wallet?: { availableBalance: { toString(): string }; frozenBalance: { toString(): string } } | null;
      preferences?: {
        phone: string | null;
        email: string | null;
        managedPassword?: string | null;
      } | null;
      parent?: {
        username: string;
        agentLevel: number | null;
        parent?: { username: string; agentLevel: number | null } | null;
      } | null;
      auth?: { lastLoginAt: Date | null } | null;
    },
    bet?: { count: number; totalStake: string; totalReturn: string },
  ) {
    const affiliationAgents = this.buildAffiliationAgents(u.parent);
    return {
      id: u.id.toString(),
      username: u.username,
      status: u.status,
      locale: u.locale,
      parentId: u.parentId?.toString() ?? null,
      parentUsername: u.parent?.username ?? null,
      affiliationAgents,
      phone: u.preferences?.phone ?? null,
      email: u.preferences?.email ?? null,
      managedPassword: u.preferences?.managedPassword ?? null,
      availableBalance: u.wallet?.availableBalance?.toString() ?? '0',
      frozenBalance: u.wallet?.frozenBalance?.toString() ?? '0',
      lastLoginAt: u.auth?.lastLoginAt ?? null,
      betCount: bet?.count ?? 0,
      totalStake: bet?.totalStake ?? '0',
      totalReturn: bet?.totalReturn ?? '0',
      createdAt: u.createdAt,
      updatedAt: u.updatedAt,
    };
  }

  private async loadBetStatsMap(userIds: bigint[]) {
    if (userIds.length === 0) return new Map<string, { count: number; totalStake: string; totalReturn: string }>();

    const groups = await this.prisma.bet.groupBy({
      by: ['userId'],
      where: { userId: { in: userIds } },
      _count: { _all: true },
      _sum: { stake: true, actualReturn: true },
    });

    return new Map(
      groups.map((g) => [
        g.userId.toString(),
        {
          count: g._count._all,
          totalStake: g._sum.stake?.toString() ?? '0',
          totalReturn: g._sum.actualReturn?.toString() ?? '0',
        },
      ]),
    );
  }

  async findById(id: bigint) {
    return this.prisma.user.findUnique({
      where: { id },
      include: { wallet: true, agentProfile: true, preferences: true },
    });
  }

  async updateProfile(
    userId: bigint,
    data: { phone?: string; email?: string; avatarKey?: string | null; username?: string },
  ) {
    const user = await this.prisma.user.findUnique({
      where: { id: userId },
      include: { preferences: true },
    });
    if (!user) throw new NotFoundException('User not found');

    if (data.username !== undefined) {
      const nextUsername = data.username.trim();
      if (!nextUsername) throw new BadRequestException('账号名称不能为空');
      try {
        assertPlayerUsername(nextUsername);
      } catch (e) {
        throw new BadRequestException(e instanceof Error ? e.message : '玩家用户名格式无效');
      }
      const settings = await this.systemConfig.getPlayerAccountSettings();
      if (!settings.allowUsernameChange) {
        throw new ForbiddenException('当前平台未开放玩家自行修改账号名称');
      }
      if (nextUsername !== user.username) {
        const taken = await this.prisma.user.findUnique({ where: { username: nextUsername } });
        if (taken) throw new BadRequestException('账号名称已被占用');
        await this.prisma.user.update({
          where: { id: userId },
          data: { username: nextUsername },
        });
      }
    }

    const phone = data.phone !== undefined ? data.phone.trim() || null : undefined;
    const email = data.email !== undefined ? data.email.trim() || null : undefined;
    let avatarKey: string | null | undefined;
    if (data.avatarKey !== undefined) {
      avatarKey = data.avatarKey?.trim() || null;
      if (avatarKey && !isValidAvatarKey(avatarKey)) {
        throw new BadRequestException('无效头像');
      }
    }

    const existing = await this.prisma.userPreference.findUnique({ where: { userId } });
    if (!existing && phone === undefined && email === undefined && avatarKey === undefined) {
      return this.findById(userId);
    }

    await this.prisma.userPreference.upsert({
      where: { userId },
      create: {
        userId,
        phone: phone ?? null,
        email: email ?? null,
        ...(avatarKey !== undefined ? { avatarKey } : {}),
      },
      update: {
        ...(phone !== undefined ? { phone } : {}),
        ...(email !== undefined ? { email } : {}),
        ...(avatarKey !== undefined ? { avatarKey } : {}),
      },
    });
    return this.findById(userId);
  }

  async updateLocale(userId: bigint, locale: string) {
    if (!(SUPPORTED_LOCALES as readonly string[]).includes(locale)) {
      throw new BadRequestException('Unsupported locale');
    }
    await this.prisma.user.update({
      where: { id: userId },
      data: { locale },
    });
    await this.prisma.userPreference.upsert({
      where: { userId },
      create: { userId, locale },
      update: { locale },
    });
    return { locale };
  }

  async listPlayers(
    page = 1,
    pageSize = 10,
    filters: PlayerListFilters = {},
  ) {
    const where: {
      userType: string;
      deletedAt: null;
      parentId?: bigint | null;
      status?: string;
      OR?: { username?: { contains: string; mode: 'insensitive' } }[];
    } = {
      userType: 'PLAYER',
      deletedAt: null,
    };
    if (filters.platformDirect) {
      where.parentId = null;
    } else if (filters.parentId) {
      where.parentId = filters.parentId;
    }
    if (filters.status) where.status = filters.status;
    if (filters.keyword?.trim()) {
      const kw = filters.keyword.trim();
      where.OR = [{ username: { contains: kw, mode: 'insensitive' } }];
    }

    const skip = (page - 1) * pageSize;
    const [rows, total] = await Promise.all([
      this.prisma.user.findMany({
        where,
        include: {
          wallet: true,
          preferences: true,
          parent: {
            select: {
              id: true,
              username: true,
              agentLevel: true,
              parent: { select: { username: true, agentLevel: true } },
            },
          },
          auth: { select: { lastLoginAt: true } },
        },
        skip,
        take: pageSize,
        orderBy: { createdAt: 'desc' },
      }),
      this.prisma.user.count({ where }),
    ]);

    const betMap = await this.loadBetStatsMap(rows.map((r) => r.id));
    return {
      items: rows.map((u) => this.formatPlayerRow(u, betMap.get(u.id.toString()))),
      total,
      page,
      pageSize,
    };
  }

  async getPlayerAdminDetail(playerId: bigint) {
    const user = await this.prisma.user.findFirst({
      where: { id: playerId, userType: 'PLAYER', deletedAt: null },
      include: {
        wallet: true,
        preferences: true,
        parent: {
          select: {
            id: true,
            username: true,
            agentLevel: true,
            parent: { select: { username: true, agentLevel: true } },
          },
        },
        auth: { select: { lastLoginAt: true, loginFailCount: true, lockedUntil: true } },
      },
    });
    if (!user) throw new NotFoundException('玩家不存在');

    const [betCount, betStake] = await Promise.all([
      this.prisma.bet.count({ where: { userId: playerId } }),
      this.prisma.bet.aggregate({
        where: { userId: playerId },
        _sum: { stake: true, actualReturn: true },
      }),
    ]);

    return {
      ...this.formatPlayerRow(user),
      lastLoginAt: user.auth?.lastLoginAt ?? null,
      loginFailCount: user.auth?.loginFailCount ?? 0,
      lockedUntil: user.auth?.lockedUntil ?? null,
      betCount,
      totalStake: betStake._sum.stake?.toString() ?? '0',
      totalReturn: betStake._sum.actualReturn?.toString() ?? '0',
    };
  }

  async updatePlayerAdmin(
    playerId: bigint,
    data: {
      status?: string;
      locale?: string;
      phone?: string;
      email?: string;
      username?: string;
      password?: string;
    },
  ) {
    const user = await this.prisma.user.findFirst({
      where: { id: playerId, userType: 'PLAYER', deletedAt: null },
      include: { auth: true },
    });
    if (!user) throw new NotFoundException('玩家不存在');

    if (data.status && !['ACTIVE', 'SUSPENDED'].includes(data.status)) {
      throw new BadRequestException('无效状态');
    }

    if (data.username !== undefined) {
      const nextUsername = data.username.trim();
      if (!nextUsername) throw new BadRequestException('账号名称不能为空');
      try {
        assertPlayerUsername(nextUsername);
      } catch (e) {
        throw new BadRequestException(e instanceof Error ? e.message : '玩家用户名格式无效');
      }
      if (nextUsername !== user.username) {
        const taken = await this.prisma.user.findUnique({ where: { username: nextUsername } });
        if (taken) throw new BadRequestException('账号名称已被占用');
        await this.prisma.user.update({
          where: { id: playerId },
          data: { username: nextUsername },
        });
      }
    }

    if (data.password !== undefined) {
      const nextPassword = data.password;
      if (nextPassword.length < 8) throw new BadRequestException('密码至少 8 位');
      if (!user.auth) throw new BadRequestException('账号认证信息缺失');
      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 },
      });
    }

    if (data.status) {
      await this.prisma.user.update({
        where: { id: playerId },
        data: { status: data.status },
      });
    }

    if (data.locale) {
      await this.prisma.user.update({
        where: { id: playerId },
        data: { locale: data.locale },
      });
    }

    const prefPatch: {
      locale?: string;
      phone?: string | null;
      email?: string | null;
    } = {};

    if (data.locale) prefPatch.locale = data.locale;
    if (data.phone !== undefined) prefPatch.phone = data.phone.trim() || null;
    if (data.email !== undefined) prefPatch.email = data.email.trim() || null;

    if (Object.keys(prefPatch).length > 0) {
      await this.prisma.userPreference.upsert({
        where: { userId: playerId },
        create: {
          userId: playerId,
          locale: data.locale ?? user.locale,
          phone: prefPatch.phone ?? null,
          email: prefPatch.email ?? null,
        },
        update: prefPatch,
      });
    }

    return this.getPlayerAdminDetail(playerId);
  }

  async getPlayerAccountPermissions() {
    return this.systemConfig.getPlayerAccountSettings();
  }

  async clearManagedPassword(userId: bigint) {
    const pref = await this.prisma.userPreference.findUnique({ where: { userId } });
    if (pref?.managedPassword) {
      await this.prisma.userPreference.update({
        where: { userId },
        data: { managedPassword: null },
      });
    }
  }
}
