import {
  Controller,
  Get,
  Post,
  Put,
  Delete,
  Body,
  Param,
  Query,
  UseGuards,
} from '@nestjs/common';
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
import { JwtAuthGuard, AgentGuard } from '../../domains/identity/guards';
import { CurrentUser } from '../../shared/common/decorators';
import { jsonResponse } from '../../shared/common/filters';
import { appBadRequest, appForbidden } from '../../shared/common/app-error';
import { validateInitialDepositRemark } from '@thebet365/shared';
import { AgentsService } from '../../domains/agent/agents.service';
import { WalletService } from '../../domains/ledger/wallet.service';
import { BetsService } from '../../domains/betting/bets.service';
import { PrismaService } from '../../shared/prisma/prisma.service';
import { IsString, IsNumber, MinLength, IsOptional, Min, IsBoolean } from 'class-validator';

class CreatePlayerDto {
  @IsString()
  username!: string;

  @IsString()
  @MinLength(8)
  password!: string;

  @IsOptional()
  @IsString()
  phone?: string;

  @IsOptional()
  @IsString()
  email?: string;

  @IsOptional()
  @IsString()
  locale?: string;

  @IsOptional()
  @IsNumber()
  @Min(0)
  initialDeposit?: number;

  @IsOptional()
  @IsString()
  remark?: string;
}

class CreateSubAgentDto extends CreatePlayerDto {
  @IsOptional()
  @IsNumber()
  creditLimit?: number;

  @IsOptional()
  @IsNumber()
  @Min(0)
  cashbackRate?: number;

  @IsOptional()
  @IsNumber()
  @Min(0)
  maxSingleDeposit?: number;

  @IsOptional()
  @IsNumber()
  @Min(0)
  maxDailyDeposit?: number;
}

class UpdatePlayerDto {
  @IsOptional()
  @IsString()
  username?: string;

  @IsOptional()
  @IsString()
  @MinLength(8)
  password?: string;

  @IsOptional()
  @IsString()
  phone?: string;

  @IsOptional()
  @IsString()
  email?: string;

  @IsOptional()
  @IsString()
  status?: string;
}

class TransferDto {
  @IsNumber()
  amount!: number;

  @IsString()
  requestId!: string;

  @IsOptional()
  @IsString()
  remark?: string;
}

class CreditDto extends TransferDto {}

class UpdateSubAgentDto {
  @IsOptional()
  @IsString()
  username?: string;

  @IsOptional()
  @IsString()
  @MinLength(8)
  password?: string;

  @IsOptional()
  @IsString()
  phone?: string;

  @IsOptional()
  @IsString()
  email?: string;

  @IsOptional()
  @IsString()
  status?: string;

  @IsOptional()
  @IsBoolean()
  freezeDirectPlayers?: boolean;

  @IsOptional()
  @IsBoolean()
  blockDirectPlayerLogin?: boolean;

  @IsOptional()
  @IsBoolean()
  unfreezeDirectPlayers?: boolean;
}

@ApiTags('Agent Portal')
@Controller('agent')
@UseGuards(JwtAuthGuard, AgentGuard)
@ApiBearerAuth()
export class AgentPortalController {
  constructor(
    private agents: AgentsService,
    private wallet: WalletService,
    private bets: BetsService,
    private prisma: PrismaService,
  ) {}

  private async canManageSubAgents(level: number) {
    const maxLevel = await this.agents.getMaxAgentLevel();
    return this.agents.canCreateSubAgent(level, maxLevel);
  }

  @Get('profile')
  async profile(@CurrentUser('id') agentId: bigint, @CurrentUser('agentLevel') level: number) {
    const profile = await this.agents.getProfile(agentId);
    const maxLevel = await this.agents.getMaxAgentLevel();
    return jsonResponse({
      level: profile.level,
      creditLimit: profile.creditLimit.toString(),
      usedCredit: profile.usedCredit.toString(),
      availableCredit: profile.availableCredit.toString(),
      cashbackRate: profile.cashbackRate.toString(),
      maxAgentLevel: maxLevel,
      canManageSubAgents: this.agents.canCreateSubAgent(level, maxLevel),
    });
  }

  @Get('players')
  async listPlayers(
    @CurrentUser('id') agentId: bigint,
    @Query('page') page?: string,
    @Query('pageSize') pageSize?: string,
  ) {
    const result = await this.agents.getDirectPlayers(agentId, {
      page: page ? Math.max(1, parseInt(page, 10) || 1) : 1,
      pageSize: pageSize
        ? Math.min(100, Math.max(1, parseInt(pageSize, 10) || 20))
        : 100,
    });
    return jsonResponse(result.items);
  }

  @Get('players/scoped')
  async listScopedPlayers(
    @CurrentUser('id') agentId: bigint,
    @Query('page') page?: string,
    @Query('pageSize') pageSize?: string,
    @Query('keyword') keyword?: string,
    @Query('status') status?: string,
    @Query('parentAgentId') parentAgentId?: string,
  ) {
    const result = await this.agents.listSubtreePlayersForPortal(agentId, {
      page: page ? parseInt(page, 10) : undefined,
      pageSize: pageSize ? parseInt(pageSize, 10) : undefined,
      keyword,
      status,
      parentAgentId,
    });
    return jsonResponse(result);
  }

  @Post('players')
  async createPlayer(@CurrentUser('id') agentId: bigint, @Body() dto: CreatePlayerDto) {
    const user = await this.agents.createPlayer(agentId, {
      username: dto.username,
      password: dto.password,
      parentId: agentId,
      locale: dto.locale,
      phone: dto.phone,
      email: dto.email,
    });

    if (dto.initialDeposit != null && dto.initialDeposit > 0) {
      const remarkResult = validateInitialDepositRemark(dto.initialDeposit, dto.remark, 'agent');
      if (!remarkResult.ok) throw appBadRequest(remarkResult.code);
      await this.agents.depositToPlayer(
        agentId,
        user.id,
        dto.initialDeposit,
        `agent-create-${user.id}-${Date.now()}`,
        remarkResult.remark,
      );
    }

    const wallet = await this.prisma.wallet.findUnique({ where: { userId: user.id } });

    return jsonResponse({
      id: user.id,
      username: user.username,
      status: user.status,
      createdAt: user.createdAt,
      availableBalance: wallet?.availableBalance ?? 0,
    });
  }

  @Get('players/:id')
  async getPlayer(@CurrentUser('id') agentId: bigint, @Param('id') playerId: string) {
    const detail = await this.agents.getDirectPlayerDetail(agentId, BigInt(playerId));
    return jsonResponse(detail);
  }

  @Get('players/:id/transfer-context')
  async getPlayerTransferContext(
    @CurrentUser('id') agentId: bigint,
    @Param('id') playerId: string,
  ) {
    const ctx = await this.agents.getPlayerTransferContext(BigInt(playerId), {
      actingAgentId: agentId,
    });
    return jsonResponse(ctx);
  }

  @Put('players/:id')
  async updatePlayer(
    @CurrentUser('id') agentId: bigint,
    @Param('id') playerId: string,
    @Body() dto: UpdatePlayerDto,
  ) {
    const detail = await this.agents.updateDirectPlayer(agentId, BigInt(playerId), dto);
    return jsonResponse(detail);
  }

  @Delete('players/:id')
  async deletePlayer(
    @CurrentUser('id') agentId: bigint,
    @Param('id') playerId: string,
  ) {
    await this.agents.deleteDirectPlayer(agentId, BigInt(playerId));
    return jsonResponse({ deleted: true });
  }

  @Get('agents')
  async listSubAgents(@CurrentUser('id') agentId: bigint, @CurrentUser('agentLevel') level: number) {
    const maxLevel = await this.agents.getMaxAgentLevel();
    if (!this.agents.canCreateSubAgent(level, maxLevel)) {
      return jsonResponse([]);
    }
    const agents = await this.agents.listChildAgentsSummary(agentId);
    return jsonResponse(agents);
  }

  @Get('agents/level-counts')
  async subtreeAgentLevelCounts(@CurrentUser('id') agentId: bigint) {
    const counts = await this.agents.countSubtreeAgentsByLevel(agentId);
    return jsonResponse(counts);
  }

  @Get('agents/options')
  async subtreeAgentOptions(@CurrentUser('id') agentId: bigint) {
    const options = await this.agents.listSubtreeAgentOptions(agentId);
    return jsonResponse(options);
  }

  @Get('agents/by-level')
  async subtreeAgentsByLevel(
    @CurrentUser('id') agentId: bigint,
    @Query('level') level: string,
    @Query('page') page?: string,
    @Query('pageSize') pageSize?: string,
    @Query('keyword') keyword?: string,
    @Query('status') status?: string,
  ) {
    const lvl = parseInt(level, 10);
    if (!Number.isFinite(lvl) || lvl < 1) {
      return jsonResponse({ items: [], total: 0, page: 1, pageSize: 20 });
    }
    const result = await this.agents.listSubtreeAgentsAtLevel(agentId, lvl, {
      page: page ? parseInt(page, 10) : undefined,
      pageSize: pageSize ? parseInt(pageSize, 10) : undefined,
      keyword,
      status,
    });
    return jsonResponse(result);
  }

  @Post('agents')
  async createSubAgent(@CurrentUser('id') agentId: bigint, @CurrentUser('agentLevel') level: number, @Body() dto: CreateSubAgentDto) {
    const maxLevel = await this.agents.getMaxAgentLevel();
    if (!this.agents.canCreateSubAgent(level, maxLevel)) {
      throw appForbidden('AGENT_MAX_LEVEL_REACHED');
    }
    const user = await this.agents.createAgent(agentId, {
      username: dto.username,
      password: dto.password,
      level: level + 1,
      parentAgentId: agentId,
      creditLimit: dto.creditLimit,
      cashbackRate: dto.cashbackRate,
      maxSingleDeposit: dto.maxSingleDeposit,
      maxDailyDeposit: dto.maxDailyDeposit,
    });
    return jsonResponse(user);
  }

  @Post('players/:id/deposit')
  async depositToPlayer(
    @CurrentUser('id') agentId: bigint,
    @Param('id') playerId: string,
    @Body() dto: TransferDto,
  ) {
    const result = await this.agents.depositToPlayer(
      agentId,
      BigInt(playerId),
      dto.amount,
      dto.requestId,
      dto.remark,
    );
    return jsonResponse(result);
  }

  @Post('players/:id/withdraw')
  async withdrawFromPlayer(
    @CurrentUser('id') agentId: bigint,
    @Param('id') playerId: string,
    @Body() dto: TransferDto,
  ) {
    const result = await this.agents.withdrawFromPlayer(
      agentId,
      BigInt(playerId),
      dto.amount,
      dto.requestId,
      dto.remark,
    );
    return jsonResponse(result);
  }

  @Get('agents/:id')
  async getSubAgent(
    @CurrentUser('id') agentId: bigint,
    @CurrentUser('agentLevel') level: number,
    @Param('id') subAgentId: string,
  ) {
    if (!(await this.canManageSubAgents(level))) {
      throw appForbidden('AGENT_MAX_LEVEL_REACHED');
    }
    const detail = await this.agents.getSubAgentForParent(agentId, BigInt(subAgentId));
    return jsonResponse(detail);
  }

  @Put('agents/:id')
  async updateSubAgent(
    @CurrentUser('id') agentId: bigint,
    @CurrentUser('agentLevel') level: number,
    @Param('id') subAgentId: string,
    @Body() dto: UpdateSubAgentDto,
  ) {
    if (!(await this.canManageSubAgents(level))) {
      throw appForbidden('AGENT_MAX_LEVEL_REACHED');
    }
    const detail = await this.agents.updateSubAgentForParent(agentId, BigInt(subAgentId), dto);
    return jsonResponse(detail);
  }

  @Get('agents/:id/downline')
  async getSubAgentDownline(
    @CurrentUser('id') agentId: bigint,
    @CurrentUser('agentLevel') level: number,
    @Param('id') subAgentId: string,
  ) {
    if (!(await this.canManageSubAgents(level))) {
      return jsonResponse({ agents: [], players: [] });
    }
    const downline = await this.agents.getDirectChildDownlineView(agentId, BigInt(subAgentId));
    return jsonResponse(downline);
  }

  @Get('agents/:id/players')
  async listSubAgentPlayers(
    @CurrentUser('id') agentId: bigint,
    @CurrentUser('agentLevel') level: number,
    @Param('id') subAgentId: string,
    @Query('page') page?: string,
    @Query('pageSize') pageSize?: string,
  ) {
    if (!(await this.canManageSubAgents(level))) {
      return jsonResponse({ items: [], total: 0, page: 1, pageSize: 20 });
    }
    const result = await this.agents.getPortalAgentDirectPlayers(
      agentId,
      BigInt(subAgentId),
      {
        page: page ? Math.max(1, parseInt(page, 10) || 1) : 1,
        pageSize: pageSize
          ? Math.min(100, Math.max(1, parseInt(pageSize, 10) || 20))
          : 20,
      },
    );
    return jsonResponse(result);
  }

  @Post('agents/:id/credit')
  async allocateCredit(
    @CurrentUser('id') agentId: bigint,
    @Param('id') subAgentId: string,
    @Body() dto: CreditDto,
  ) {
    const subAgent = await this.prisma.agentProfile.findUnique({
      where: { userId: BigInt(subAgentId) },
    });
    if (!subAgent || subAgent.parentAgentId !== agentId) {
      return jsonResponse(null, 'Not your sub-agent');
    }
    const result = await this.agents.adjustCredit(
      BigInt(subAgentId),
      dto.amount,
      agentId,
      dto.requestId,
      dto.remark,
    );
    return jsonResponse(result);
  }

  @Get('bets')
  async listBets(
    @CurrentUser('id') agentId: bigint,
    @Query('page') page?: string,
    @Query('pageSize') pageSize?: string,
  ) {
    const p = Math.max(1, page ? parseInt(page, 10) : 1);
    const size = Math.min(Math.max(1, pageSize ? parseInt(pageSize, 10) : 10), 100);
    const skip = (p - 1) * size;
    const agentIds = await this.agents.getSubtreeAgentIds(agentId);

    const [items, total] = await Promise.all([
      this.prisma.bet.findMany({
        where: { agentId: { in: agentIds } },
        include: { selections: true, user: true },
        orderBy: { placedAt: 'desc' },
        skip,
        take: size,
      }),
      this.prisma.bet.count({ where: { agentId: { in: agentIds } } }),
    ]);
    return jsonResponse({ items, total, page: p, pageSize: size });
  }

  @Get('reports/summary')
  async reportSummary(@CurrentUser('id') agentId: bigint) {
    const summary = await this.agents.getReportSummary(agentId);
    return jsonResponse(summary);
  }

  @Get('credit-transactions')
  async listCreditTransactions(
    @CurrentUser('id') agentId: bigint,
    @Query('page') page?: string,
    @Query('pageSize') pageSize?: string,
    @Query('agentId') filterAgentId?: string,
    @Query('keyword') keyword?: string,
    @Query('operatorKeyword') operatorKeyword?: string,
    @Query('transactionType') transactionType?: string,
    @Query('dateFrom') dateFrom?: string,
    @Query('dateTo') dateTo?: string,
  ) {
    const scopedAgentIds = await this.agents.getSubtreeAgentIds(agentId);
    const parsedAgentId = filterAgentId ? BigInt(filterAgentId) : undefined;
    if (parsedAgentId && !scopedAgentIds.some((id) => id === parsedAgentId)) {
      return jsonResponse({ items: [], total: 0, page: 1, pageSize: 20 });
    }
    const result = await this.agents.listCreditTransactions({
      page: page ? parseInt(page, 10) : 1,
      pageSize: pageSize ? parseInt(pageSize, 10) : 20,
      agentId: parsedAgentId,
      keyword,
      operatorKeyword,
      transactionType,
      scopedAgentIds,
      dateFrom: dateFrom ? new Date(dateFrom) : undefined,
      dateTo: dateTo ? new Date(dateTo) : undefined,
    });
    return jsonResponse(result);
  }

  @Get('wallet-transactions')
  async walletTransactions(
    @CurrentUser('id') agentId: bigint,
    @Query('page') page?: string,
    @Query('pageSize') pageSize?: string,
    @Query('playerId') playerId?: string,
    @Query('parentAgentId') parentAgentId?: string,
    @Query('parentAgentKeyword') parentAgentKeyword?: string,
    @Query('keyword') keyword?: string,
    @Query('operatorKeyword') operatorKeyword?: string,
    @Query('transactionType') transactionType?: string,
    @Query('dateFrom') dateFrom?: string,
    @Query('dateTo') dateTo?: string,
  ) {
    const scopedParentAgentIds = await this.agents.getSubtreeAgentIds(agentId);
    const parsedParentAgentId = parentAgentId ? BigInt(parentAgentId) : undefined;
    if (
      parsedParentAgentId &&
      !scopedParentAgentIds.some((id) => id === parsedParentAgentId)
    ) {
      return jsonResponse({ items: [], total: 0, page: 1, pageSize: 20 });
    }
    if (playerId) {
      const player = await this.prisma.user.findFirst({
        where: { id: BigInt(playerId), userType: 'PLAYER', deletedAt: null },
        select: { id: true, parentId: true },
      });
      if (
        !player?.parentId ||
        !scopedParentAgentIds.some((id) => id === player.parentId)
      ) {
        return jsonResponse({ items: [], total: 0, page: 1, pageSize: 20 });
      }
    }
    const result = await this.wallet.listTransferTransactions({
      page: page ? parseInt(page, 10) : 1,
      pageSize: pageSize ? parseInt(pageSize, 10) : 20,
      playerId: playerId ? BigInt(playerId) : undefined,
      parentAgentId: parsedParentAgentId,
      parentAgentKeyword,
      scopedParentAgentIds,
      keyword,
      operatorKeyword,
      transactionType,
      dateFrom: dateFrom ? new Date(dateFrom) : undefined,
      dateTo: dateTo ? new Date(dateTo) : undefined,
    });
    return jsonResponse(result);
  }

  @Get('wallet/ledger-transactions')
  async walletLedgerTransactions(
    @CurrentUser('id') agentId: bigint,
    @Query('page') page?: string,
    @Query('pageSize') pageSize?: string,
    @Query('playerId') playerId?: string,
    @Query('parentAgentId') parentAgentId?: string,
    @Query('parentAgentKeyword') parentAgentKeyword?: string,
    @Query('keyword') keyword?: string,
    @Query('operatorKeyword') operatorKeyword?: string,
    @Query('transactionType') transactionType?: string,
    @Query('typeCategory') typeCategory?: string,
    @Query('dateFrom') dateFrom?: string,
    @Query('dateTo') dateTo?: string,
  ) {
    const scopedParentAgentIds = await this.agents.getSubtreeAgentIds(agentId);
    const parsedParentAgentId = parentAgentId ? BigInt(parentAgentId) : undefined;
    if (
      parsedParentAgentId &&
      !scopedParentAgentIds.some((id) => id === parsedParentAgentId)
    ) {
      return jsonResponse({ items: [], total: 0, page: 1, pageSize: 20 });
    }
    if (playerId) {
      await this.agents.requirePlayerInPortalSubtree(agentId, BigInt(playerId));
    }
    const result = await this.wallet.listWalletTransactionsAdmin({
      page: page ? parseInt(page, 10) : 1,
      pageSize: pageSize ? parseInt(pageSize, 10) : 20,
      playerId: playerId ? BigInt(playerId) : undefined,
      parentAgentId: parsedParentAgentId,
      parentAgentKeyword,
      scopedParentAgentIds,
      keyword,
      operatorKeyword,
      transactionType,
      typeCategory,
      dateFrom: dateFrom ? new Date(dateFrom) : undefined,
      dateTo: dateTo ? new Date(dateTo) : undefined,
    });
    return jsonResponse(result);
  }
}
