import {
  Controller,
  Get,
  Post,
  Put,
  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 { 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;
}

class CreditDto extends TransferDto {
  @IsOptional()
  @IsString()
  remark?: string;
}

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;
}

@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,
  ) {}

  @Get('profile')
  async profile(@CurrentUser('id') agentId: bigint) {
    const profile = await this.agents.getProfile(agentId);
    return jsonResponse(profile);
  }

  @Get('players')
  async listPlayers(@CurrentUser('id') agentId: bigint) {
    const players = await this.agents.getDirectPlayers(agentId);
    return jsonResponse(players);
  }

  @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) {
      await this.agents.depositToPlayer(
        agentId,
        user.id,
        dto.initialDeposit,
        `agent-create-${user.id}-${Date.now()}`,
        dto.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);
  }

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

  @Post('agents')
  async createSubAgent(@CurrentUser('id') agentId: bigint, @CurrentUser('agentLevel') level: number, @Body() dto: CreateSubAgentDto) {
    if (level !== 1) {
      return jsonResponse(null, 'Only level 1 agents can create sub-agents');
    }
    const user = await this.agents.createAgent(agentId, {
      username: dto.username,
      password: dto.password,
      level: 2,
      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);
    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);
    return jsonResponse(result);
  }

  @Get('agents/:id')
  async getSubAgent(
    @CurrentUser('id') agentId: bigint,
    @CurrentUser('agentLevel') level: number,
    @Param('id') subAgentId: string,
  ) {
    if (level !== 1) {
      return jsonResponse(null, 'Only level 1 agents can manage sub-agents');
    }
    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 (level !== 1) {
      return jsonResponse(null, 'Only level 1 agents can manage sub-agents');
    }
    const detail = await this.agents.updateSubAgentForParent(agentId, BigInt(subAgentId), dto);
    return jsonResponse(detail);
  }

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

  @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 descendants = await this.prisma.agentClosure.findMany({
      where: { ancestorId: agentId },
    });
    const agentIds = descendants.map((d) => d.descendantId);

    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('wallet-transactions')
  async walletTransactions(@CurrentUser('id') agentId: bigint, @Query('playerId') playerId?: string) {
    const players = playerId
      ? [BigInt(playerId)]
      : (await this.agents.getDirectPlayers(agentId)).map((p) => p.id);

    const transactions = await this.prisma.walletTransaction.findMany({
      where: { userId: { in: players } },
      orderBy: { createdAt: 'desc' },
      take: 50,
    });
    return jsonResponse(transactions);
  }
}
