import {
    BadRequestException,
    Body,
    Controller,
    Delete,
    Get,
    Headers,
    NotFoundException,
    Param,
    Post,
    Put,
    Query,
    RawBodyRequest,
    Req,
    UploadedFile,
    UseGuards,
    UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { Request } from 'express';
import { Throttle } from '@nestjs/throttler';
import { FeesService } from './fees.service';
import { CreateFeeStructureDto } from './dto/create-fee-structure.dto';
import { UpdateFeeStructureDto } from './dto/update-fee-structure.dto';
import { RecordPaymentDto } from './dto/record-payment.dto';
import { FilterAssignmentsDto } from './dto/filter-assignments.dto';
import { VerifyFeePaymentDto } from './dto/verify-fee-payment.dto';
import { CreateOrderDto } from './dto/create-order.dto';
import { DuplicateFeeStructureDto } from './dto/duplicate-fee-structure.dto';
import { ApplyDiscountDto } from './dto/apply-discount.dto';
import { UpdateDiscountDto } from './dto/update-discount.dto';
import { JwtAuthGuard } from 'src/common/guards/jwt-auth.guard';
import { RolesGuard } from 'src/common/guards/roles.guard';
import { Roles } from 'src/common/decorators/roles.decorator';
import { Role } from '../common/roles/role.enum';
import { CurrentUser } from 'src/common/decorators/current-user.decorator';
import { Public } from 'src/common/decorators/public.decorator';
import { MongoIdPipe } from 'src/common/pipes/mongo-id.pipe';

/**
 * Verify an uploaded file's first bytes match its declared MIME type.
 * Covers JPEG, PNG, WebP, PDF — the receipt-upload allowlist.
 * Client-set mimetype on multer is spoofable, so we sniff magic bytes.
 */
function verifyReceiptMagic(buf: Buffer, declaredMime: string): boolean {
    if (!buf || buf.length < 4) return false;
    const b = buf;
    const isJpeg = b[0] === 0xff && b[1] === 0xd8 && b[2] === 0xff;
    const isPng = b[0] === 0x89 && b[1] === 0x50 && b[2] === 0x4e && b[3] === 0x47;
    // WebP: "RIFF"...."WEBP"
    const isWebp =
        b.length >= 12 &&
        b[0] === 0x52 && b[1] === 0x49 && b[2] === 0x46 && b[3] === 0x46 &&
        b[8] === 0x57 && b[9] === 0x45 && b[10] === 0x42 && b[11] === 0x50;
    // PDF: "%PDF-"
    const isPdf = b[0] === 0x25 && b[1] === 0x50 && b[2] === 0x44 && b[3] === 0x46;

    switch (declaredMime) {
        case 'image/jpeg': return isJpeg;
        case 'image/png': return isPng;
        case 'image/webp': return isWebp;
        case 'application/pdf': return isPdf;
        default: return false;
    }
}

@UseGuards(JwtAuthGuard, RolesGuard)
@Controller('fees')
export class FeesController {
    constructor(private readonly feesService: FeesService) {}

    /* =====================
       Admin: Fee Structures
    ===================== */

    @Post('structures')
    @Roles(Role.ADMIN)
    @Throttle({ default: { limit: 10, ttl: 60000 } })
    createStructure(@Body() dto: CreateFeeStructureDto) {
        return this.feesService.createStructure(dto);
    }

    @Get('structures')
    @Roles(Role.ADMIN)
    getStructures(
        @Query('academicYearId') academicYearId?: string,
        @Query('classId') classId?: string,
        @Query('activeOnly') activeOnly?: string,
        @Headers('x-academic-year-id') ayHeader?: string,
    ) {
        return this.feesService.getStructures(academicYearId ?? ayHeader, classId, activeOnly === 'true');
    }

    @Post('structures/:id/duplicate')
    @Roles(Role.ADMIN)
    @Throttle({ default: { limit: 10, ttl: 60000 } })
    duplicateStructure(
        @Param('id', MongoIdPipe) id: string,
        @Body() dto: DuplicateFeeStructureDto,
    ) {
        return this.feesService.duplicateStructure(id, dto.targetAcademicYearId);
    }

    @Get('structures/:id')
    @Roles(Role.ADMIN)
    getStructure(@Param('id', MongoIdPipe) id: string) {
        return this.feesService.getStructure(id);
    }

    @Put('structures/:id')
    @Roles(Role.ADMIN)
    updateStructure(
        @Param('id', MongoIdPipe) id: string,
        @Body() dto: UpdateFeeStructureDto,
    ) {
        return this.feesService.updateStructure(id, dto);
    }

    @Delete('structures/:id')
    @Roles(Role.ADMIN)
    deleteStructure(@Param('id', MongoIdPipe) id: string) {
        return this.feesService.deleteStructure(id);
    }

    @Post('structures/:id/regenerate-unpaid')
    @Roles(Role.ADMIN)
    @Throttle({ default: { limit: 5, ttl: 60000 } })
    regenerateUnpaid(@Param('id', MongoIdPipe) id: string) {
        return this.feesService.regenerateUnpaidAssignments(id);
    }

    /* =====================
       Admin: Assignments
    ===================== */

    @Get('assignments')
    @Roles(Role.ADMIN)
    getAssignments(
        @Query() filter: FilterAssignmentsDto,
        @Headers('x-academic-year-id') ayHeader?: string,
    ) {
        if (!filter.academicYearId && ayHeader) filter.academicYearId = ayHeader;
        return this.feesService.getAssignments(filter);
    }

    @Post('sync-assignments')
    @Roles(Role.ADMIN)
    @Throttle({ default: { limit: 2, ttl: 60000 } })
    syncAssignments(
        @Query('structureId') structureId?: string,
        @Headers('x-academic-year-id') ayHeader?: string,
    ) {
        return this.feesService.syncAssignments(structureId, ayHeader);
    }

    @Post('assignments/:id/apply-discount')
    @Roles(Role.ADMIN)
    @Throttle({ default: { limit: 20, ttl: 60000 } })
    applyDiscount(
        @Param('id', MongoIdPipe) id: string,
        @Body() dto: ApplyDiscountDto,
        @CurrentUser('id') adminId: string,
    ) {
        return this.feesService.applyDiscount(id, dto, adminId);
    }

    @Put('assignments/:id/update-discount')
    @Roles(Role.ADMIN)
    @Throttle({ default: { limit: 20, ttl: 60000 } })
    updateDiscount(
        @Param('id', MongoIdPipe) id: string,
        @Body() dto: UpdateDiscountDto,
        @CurrentUser('id') adminId: string,
    ) {
        return this.feesService.updateDiscount(id, dto, adminId);
    }

    @Delete('assignments/:id/discount')
    @Roles(Role.ADMIN)
    @Throttle({ default: { limit: 20, ttl: 60000 } })
    removeDiscount(@Param('id', MongoIdPipe) id: string) {
        return this.feesService.removeDiscount(id);
    }

    @Post('assignments/:id/record-payment')
    @Roles(Role.ADMIN)
    @Throttle({ default: { limit: 20, ttl: 60000 } })
    @UseInterceptors(FileInterceptor('receipt'))
    recordOfflinePayment(
        @Param('id', MongoIdPipe) id: string,
        @Body() dto: RecordPaymentDto,
        @CurrentUser('id') adminId: string,
        @UploadedFile() receipt?: Express.Multer.File,
    ) {
        if (receipt) {
            if (!receipt.size || receipt.buffer.length === 0) {
                throw new BadRequestException('Receipt file is empty');
            }
            const allowedMimes = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf'];
            if (!allowedMimes.includes(receipt.mimetype)) {
                throw new BadRequestException('Receipt must be JPEG, PNG, WebP, or PDF');
            }
            if (receipt.size > 5 * 1024 * 1024) {
                throw new BadRequestException('Receipt file must not exceed 5MB');
            }
            // Magic-bytes check: client-declared MIME is trivially spoofable, so
            // verify the actual file content matches the declared type.
            if (!verifyReceiptMagic(receipt.buffer, receipt.mimetype)) {
                throw new BadRequestException('Receipt file content does not match its declared type');
            }
        }
        return this.feesService.recordOfflinePayment(id, dto, adminId, receipt);
    }

    @Get('student/:studentId/dashboard')
    @Roles(Role.ADMIN)
    getStudentDashboardAdmin(
        @Param('studentId', MongoIdPipe) studentId: string,
        @Query('academicYearId') academicYearId?: string,
        @Headers('x-academic-year-id') ayHeader?: string,
    ) {
        return this.feesService.getStudentDashboard(studentId, academicYearId ?? ayHeader);
    }

    /* =====================
       Student: Dashboard & Payments
    ===================== */

    @Get('my-dashboard')
    @Roles(Role.STUDENT)
    getMyDashboard(
        @CurrentUser('id') studentId: string,
        @Query('academicYearId') academicYearId?: string,
        @Headers('x-academic-year-id') ayHeader?: string,
    ) {
        return this.feesService.getStudentDashboard(studentId, academicYearId ?? ayHeader);
    }

    @Post('assignments/:id/create-order')
    @Roles(Role.STUDENT)
    @Throttle({ default: { limit: 5, ttl: 60000 } })
    createOrder(
        @Param('id', MongoIdPipe) id: string,
        @CurrentUser('id') studentId: string,
        @Body() dto: CreateOrderDto,
    ) {
        return this.feesService.createRazorpayOrder(id, studentId, dto.amount);
    }

    @Post('assignments/:id/verify-payment')
    @Roles(Role.STUDENT)
    @Throttle({ default: { limit: 10, ttl: 60000 } })
    verifyPayment(
        @Param('id', MongoIdPipe) id: string,
        @CurrentUser('id') studentId: string,
        @Body() body: VerifyFeePaymentDto,
    ) {
        return this.feesService.verifyRazorpayPayment(id, studentId, body);
    }

    @Get('assignments/:id/payments')
    @Roles(Role.ADMIN, Role.STUDENT)
    getPaymentHistory(
        @Param('id', MongoIdPipe) id: string,
        @CurrentUser('id') userId: string,
        @CurrentUser('role') role: string,
    ) {
        const studentId = role === Role.STUDENT ? userId : undefined;
        return this.feesService.getPaymentHistory(id, studentId);
    }

    /* =====================
       Public: Receipt Verification
    ===================== */

    @Get('verify-receipt')
    @Public()
    @Throttle({ default: { limit: 10, ttl: 60000 } })
    async verifyReceipt(@Query('receipt') receipt: string) {
        if (!receipt || typeof receipt !== 'string' || receipt.length > 50) {
            throw new BadRequestException('Invalid receipt number');
        }
        const result = await this.feesService.verifyReceipt(receipt.trim());
        if (!result) throw new NotFoundException('Receipt not found');
        return result;
    }

    /* =====================
       Webhook (public - no auth)
    ===================== */

    @Post('webhook')
    @Public()
    @Throttle({ default: { limit: 60, ttl: 60000 } })
    handleWebhook(@Req() req: RawBodyRequest<Request>) {
        const signature = req.headers['x-razorpay-signature'] as string | undefined;
        if (!req.rawBody) {
            throw new BadRequestException(
                'Raw body not available — ensure rawBody: true is set in NestJS bootstrap.',
            );
        }
        return this.feesService.handleWebhook(signature, req.rawBody, req.body);
    }
}
