import { Controller, Post, Get, Body, Query, HttpCode, HttpStatus, UseGuards, Req, Logger, } from "@nestjs/common"; import { ApiTags, ApiBearerAuth, ApiOperation, ApiResponse, } from "@nestjs/swagger"; import { CouponsService } from "./coupons.service"; import { MatchesService } from "../matches/matches.service"; import { SmartCouponService } from "./services/smart-coupon.service"; import { UserCouponService, CreateCouponDto, } from "./services/user-coupon.service"; import { AnalyzeMatchDto, DailyBankoDto, SuggestCouponDto, FrequencyCouponDto, } from "./dto/coupons-request.dto"; import { Public } from "../../common/decorators"; import { JwtAuthGuard } from "../auth/guards/auth.guards"; // Assuming standard guard import { Sport } from "../matches/dto"; @ApiTags("Coupon") @Controller("coupon") export class CouponsController { private readonly logger = new Logger(CouponsController.name); constructor( private readonly couponsService: CouponsService, private readonly smartCouponService: SmartCouponService, private readonly userCouponService: UserCouponService, private readonly matchesService: MatchesService, ) {} /** * POST /coupon/analyze-match * Analyze a single match with V20+ single-match package */ @Post("analyze-match") @Public() @HttpCode(HttpStatus.OK) @ApiOperation({ summary: "Analyze single match with V20 model" }) @ApiResponse({ status: 200, description: "Match analysis" }) async analyzeMatch(@Body() dto: AnalyzeMatchDto) { const analysis = await this.smartCouponService.analyzeMatch(dto.matchId); if (!analysis) { return { success: false, message: "Analiz yapılamadı." }; } return { success: true, data: analysis }; } /** * POST /coupon/analyze (alias for /analyze-match - frontend compatibility) */ @Post("analyze") @Public() @HttpCode(HttpStatus.OK) @ApiOperation({ summary: "Analyze single match with V20 model (alias)", deprecated: true, }) async analyzeMatchAlias(@Body() dto: AnalyzeMatchDto) { return this.analyzeMatch(dto); } /** * POST /coupon * Alias for /coupon/create - frontend compatibility */ @Post() @UseGuards(JwtAuthGuard) @ApiBearerAuth() @HttpCode(HttpStatus.CREATED) @ApiOperation({ summary: "Create and save a user coupon (alias)" }) async createCouponAlias(@Body() dto: CreateCouponDto, @Req() req: any) { return this.createCoupon(dto, req); } /** * POST /coupon/daily-banko * Generate a high-confidence banko combo (2 matches) */ @Post("daily-banko") @Public() @HttpCode(HttpStatus.OK) @ApiOperation({ summary: "Generate a high-confidence banko combo (2 matches)", }) async getDailyBanko(@Body() dto: DailyBankoDto) { // If no match IDs provided, fetch from system (top 50 upcoming) let candidateMatches = dto.matchIds || []; if (candidateMatches.length === 0) { candidateMatches = await this.matchesService.findUpcomingMatches( Sport.FOOTBALL, 20, ); this.logger.debug( `Auto-fetched ${candidateMatches.length} matches for daily-banko`, ); } else { candidateMatches = await this.matchesService.filterUpcomingMatchIds( candidateMatches, Sport.FOOTBALL, ); this.logger.debug( `Sanitized candidate matches for daily-banko: ${candidateMatches.length}`, ); } if (candidateMatches.length === 0) { return { success: false, message: "Kupon için uygun, henüz baÅŸlamamış maç bulunamadı.", }; } const coupon = await this.smartCouponService.generateDailyBankoCoupon(candidateMatches); if (!coupon) { return { success: false, message: "Kriterlere uygun (80%+ güvenli) yeterli maç bulunamadı.", }; } return { success: true, data: coupon }; } /** * POST /coupon/suggest * Generate Smart Coupon */ @Post("suggest") @Public() @HttpCode(HttpStatus.OK) @ApiOperation({ summary: "Suggest Smart Coupon" }) @ApiResponse({ status: 200, description: "Smart Coupon generated" }) async suggestCoupon(@Body() dto: SuggestCouponDto) { // If no match IDs provided, fetch from system (top 50 upcoming) let candidateMatches = dto.matchIds || []; if (candidateMatches.length === 0) { candidateMatches = await this.matchesService.findUpcomingMatches( Sport.FOOTBALL, 20, ); this.logger.debug( `Auto-fetched ${candidateMatches.length} matches for suggest`, ); } else { candidateMatches = await this.matchesService.filterUpcomingMatchIds( candidateMatches, Sport.FOOTBALL, ); this.logger.debug( `Sanitized candidate matches for suggest: ${candidateMatches.length}`, ); } if (candidateMatches.length === 0) { return { success: false, message: "Tahmin için uygun, henüz baÅŸlamamış maç bulunamadı.", }; } const coupon = await this.smartCouponService.getSmartCoupon( candidateMatches, dto.strategy, { maxMatches: dto.maxMatches, minConfidence: dto.minConfidence, }, ); if (!coupon) { return { success: false, message: "Kupon oluşturulamadı." }; } return { success: true, data: coupon }; } /** * POST /coupon/frequency-coupon * Generate a frequency-based parlay coupon (Conditional Frequency Engine) */ @Post("frequency-coupon") @Public() @HttpCode(HttpStatus.OK) @ApiOperation({ summary: "Generate frequency-based parlay coupon", description: "Scans upcoming matches, applies conditional frequency analysis " + "(team odds-band performance), and builds 2-5 match combos with +EV calculation.", }) @ApiResponse({ status: 200, description: "Frequency coupon generated" }) async getFrequencyCoupon(@Body() dto: FrequencyCouponDto) { const coupon = await this.smartCouponService.generateFrequencyBasedCoupon({ matchIds: dto.matchIds, maxMatches: dto.maxMatches, minSignal: dto.minSignal, markets: dto.markets, }); if (!coupon || coupon.bets.length === 0) { return { success: false, message: "Frekans analizine uygun yeterli maç bulunamadı. " + "minSignal değerini düşürmeyi veya daha fazla maç beklemeyi deneyin.", data: coupon, }; } return { success: true, data: coupon }; } // ============================================ // USER COUPON ENDPOINTS // ============================================ /** * POST /coupon/create * Save a user generated coupon */ @Post("create") @UseGuards(JwtAuthGuard) @ApiBearerAuth() @HttpCode(HttpStatus.CREATED) @ApiOperation({ summary: "Create and save a user coupon" }) async createCoupon(@Body() dto: CreateCouponDto, @Req() req: any) { // req.user is populated by JwtAuthGuard const coupon = await this.userCouponService.createCoupon(req.user, dto); return { success: true, data: coupon }; } /** * GET /coupon/my-stats * Get user betting statistics (ROI, Win Rate) */ @Get("my-stats") @UseGuards(JwtAuthGuard) @ApiBearerAuth() @ApiOperation({ summary: "Get user betting statistics" }) async getUserStats(@Req() req: any) { const stats = await this.userCouponService.getUserStatistics(req.user.id); return { success: true, data: stats }; } /** * GET /coupon/history * Get coupon history (Public/System coupons) */ @Get("history") @ApiBearerAuth() @ApiOperation({ summary: "Get coupon history" }) @ApiResponse({ status: 200, description: "History retrieved" }) async getHistory(@Query("limit") limit?: string) { // eslint-disable-next-line @typescript-eslint/await-thenable const results = await this.couponsService.getCouponHistory( Number(limit) || 10, ); return { success: true, data: results }; } }