import {
  Controller,
  Get,
  Post,
  Patch,
  Body,
  Param,
  Query,
  UseGuards,
} from '@nestjs/common';
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
import { JwtAuthGuard, PlayerGuard } from '../../domains/identity/guards';
import { CurrentUser } from '../../shared/common/decorators';
import { jsonResponse } from '../../shared/common/filters';
import { UsersService } from '../../domains/identity/users.service';
import { SystemConfigService } from '../../shared/config/system-config.service';
import { WalletService } from '../../domains/ledger/wallet.service';
import { MatchesService } from '../../domains/catalog/matches.service';
import { OutrightService } from '../../domains/catalog/outright.service';
import { BetsService } from '../../domains/betting/bets.service';
import { ContentService } from '../../domains/operations/content/content.service';
import { CashbackService } from '../../domains/operations/cashback/cashback.service';
import { IsString, IsNumber, IsArray, ValidateNested, Min, IsOptional } from 'class-validator';
import { Type } from 'class-transformer';

class SingleBetDto {
  @IsString()
  selectionId!: string;

  @IsString()
  oddsVersion!: string;

  @IsNumber()
  @Min(0.01)
  stake!: number;

  @IsString()
  requestId!: string;
}

class ParlayLegDto {
  @IsString()
  selectionId!: string;

  @IsString()
  oddsVersion!: string;
}

class ParlayBetDto {
  @IsArray()
  @ValidateNested({ each: true })
  @Type(() => ParlayLegDto)
  legs!: ParlayLegDto[];

  @IsNumber()
  @Min(0.01)
  stake!: number;

  @IsString()
  requestId!: string;
}

class LocaleDto {
  @IsString()
  locale!: string;
}

class UpdateProfileDto {
  @IsOptional()
  @IsString()
  phone?: string;

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

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

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

@ApiTags('Player')
@Controller('player')
@UseGuards(JwtAuthGuard, PlayerGuard)
@ApiBearerAuth()
export class PlayerController {
  constructor(
    private users: UsersService,
    private wallet: WalletService,
    private matches: MatchesService,
    private outright: OutrightService,
    private bets: BetsService,
    private content: ContentService,
    private cashback: CashbackService,
    private systemConfig: SystemConfigService,
  ) {}

  private async formatPlayerProfile(user: NonNullable<Awaited<ReturnType<UsersService['findById']>>>) {
    const accountSettings = await this.systemConfig.getPlayerAccountSettings();
    const prefs = user.preferences;
    const viewablePassword = prefs?.managedPassword ?? null;
    const safePrefs = prefs
      ? (({
          managedPassword: _m,
          allowPasswordChange: _a,
          allowUsernameChange: _b,
          ...rest
        }) => rest)(prefs)
      : {};
    return {
      ...user,
      id: user.id.toString(),
      parentId: user.parentId?.toString() ?? null,
      preferences: {
        ...safePrefs,
        viewablePassword,
        allowPasswordChange: accountSettings.allowPasswordChange,
        allowUsernameChange: accountSettings.allowUsernameChange,
      },
    };
  }

  @Get('profile')
  async profile(@CurrentUser('id') userId: bigint) {
    const user = await this.users.findById(userId);
    if (!user) return jsonResponse(null);
    return jsonResponse(await this.formatPlayerProfile(user));
  }

  @Post('language')
  async setLanguage(@CurrentUser('id') userId: bigint, @Body() dto: LocaleDto) {
    const result = await this.users.updateLocale(userId, dto.locale);
    return jsonResponse(result);
  }

  @Patch('profile')
  async updateProfile(@CurrentUser('id') userId: bigint, @Body() dto: UpdateProfileDto) {
    const user = await this.users.updateProfile(userId, dto);
    if (!user) return jsonResponse(null);
    return jsonResponse(await this.formatPlayerProfile(user));
  }

  @Get('home')
  async home(@CurrentUser('locale') locale: string) {
    const [banners, announcements, hotMatches, todayMatches] = await Promise.all([
      this.content.listActive('BANNER', locale),
      this.content.listActiveAnnouncements(locale),
      this.matches.listPublished(locale),
      this.matches.listPublished(locale),
    ]);
    return jsonResponse({
      banners,
      announcements,
      /** @deprecated 使用 announcements */
      ticker: announcements,
      /** @deprecated 使用 announcements */
      notices: announcements,
      hotMatches: (hotMatches as Array<{ isHot?: boolean }>).filter((m) => m.isHot),
      todayMatches,
    });
  }

  @Get('matches')
  async listMatches(
    @CurrentUser('locale') locale: string,
    @Query('leagueId') leagueId?: string,
  ) {
    const items = await this.matches.listPublished(locale, leagueId ? BigInt(leagueId) : undefined);
    return jsonResponse(items);
  }

  @Get('outrights')
  async listOutrights(@CurrentUser('locale') locale: string) {
    const items = await this.outright.listForPlayer(locale);
    return jsonResponse(items);
  }

  @Get('matches/:id')
  async matchDetail(@Param('id') id: string, @CurrentUser('locale') locale: string) {
    const match = await this.matches.getMatchDetail(BigInt(id), locale);
    return jsonResponse(match);
  }

  @Post('bets/single')
  async singleBet(@CurrentUser('id') userId: bigint, @CurrentUser('parentId') parentId: bigint, @Body() dto: SingleBetDto) {
    const bet = await this.bets.placeSingleBet(
      userId,
      parentId,
      BigInt(dto.selectionId),
      BigInt(dto.oddsVersion),
      dto.stake,
      dto.requestId,
    );
    return jsonResponse(bet);
  }

  @Post('bets/parlay')
  async parlayBet(@CurrentUser('id') userId: bigint, @CurrentUser('parentId') parentId: bigint, @Body() dto: ParlayBetDto) {
    const bet = await this.bets.placeParlayBet(
      userId,
      parentId,
      dto.legs.map((l) => ({ selectionId: BigInt(l.selectionId), oddsVersion: BigInt(l.oddsVersion) })),
      dto.stake,
      dto.requestId,
    );
    return jsonResponse(bet);
  }

  @Get('bets')
  async myBets(
    @CurrentUser('id') userId: bigint,
    @CurrentUser('locale') locale: string,
    @Query('status') status?: string,
    @Query('page') page?: string,
  ) {
    const result = await this.bets.getUserBets(userId, status, page ? parseInt(page) : 1);
    const items = await this.matches.enrichBetsForHistory(result.items, locale);
    return jsonResponse({ ...result, items });
  }

  @Get('bets/stats')
  async betStats(@CurrentUser('id') userId: bigint) {
    const stats = await this.bets.getUserBetStats(userId);
    return jsonResponse(stats);
  }

  @Get('bets/:betNo')
  async betDetail(
    @CurrentUser('id') userId: bigint,
    @CurrentUser('locale') locale: string,
    @Param('betNo') betNo: string,
  ) {
    const bet = await this.bets.getBetByNo(betNo, userId);
    if (!bet) return jsonResponse(null);
    const [enriched] = await this.matches.enrichBetsForHistory([bet], locale);
    return jsonResponse(enriched);
  }

  @Get('wallet/transactions/stats')
  async transactionStats(@CurrentUser('id') userId: bigint) {
    const stats = await this.wallet.getTransactionStats(userId);
    return jsonResponse(stats);
  }

  @Get('wallet/transactions')
  async transactions(
    @CurrentUser('id') userId: bigint,
    @Query('page') page?: string,
    @Query('type') type?: string,
  ) {
    const result = await this.wallet.getTransactions(userId, page ? parseInt(page) : 1, 20, type);
    return jsonResponse(result);
  }

  @Get('wallet/transactions/:transactionId')
  async transactionDetail(
    @CurrentUser('id') userId: bigint,
    @Param('transactionId') transactionId: string,
  ) {
    const detail = await this.wallet.getTransactionDetail(userId, transactionId);
    return jsonResponse(detail);
  }

  @Get('cashbacks')
  async cashbacks(@CurrentUser('id') userId: bigint) {
    const items = await this.cashback.getUserCashbacks(userId);
    return jsonResponse(items);
  }
}
