import {
  Controller,
  Post,
  Get,
  Body,
  Param,
  Query,
  Req,
  HttpCode,
  UseGuards,
  BadRequestException,
} from '@nestjs/common';
import type { Request } from 'express';
import { Logger } from '@nestjs/common';
import type { Response } from 'express';
import { ClientPortalService } from './services/client-portal.service';
import { ClientAuthService } from './services/client-auth.service';
import { JwtAuthGuard } from './../auth/guards/jwt-auth.guard';
// 🔧 PORTAL-FIX: Import ClientAuthGuard — it checks company_users.status on every request
import { ClientAuthGuard } from './guards/client-auth.guard';
import { ThrottlerGuard } from '@nestjs/throttler';
import {
  InviteClientDto,
  ClientLoginDto,
  VerifyOtpDto,
  ListClientAuditsDto,
  ResendOtpDto,
  RefreshClientTokenDto,
  ClientLogoutDto,
} from './dto/client-portal.dto';
import { Public } from '../common/guards/decorators/public.decorator';
import { getErrorMessage } from '../common/utils/error.helper';
import { NcService } from 'src/nc/nc.service';
import { UploadedFiles, UseInterceptors, Res } from '@nestjs/common';
import { FilesInterceptor } from '@nestjs/platform-express';
import { PreviousNcPdfService } from '../previous-nc/services/previous-nc-pdf.service';

@Controller('client-portal')
export class ClientPortalController {
  private readonly logger = new Logger('ClientPortalController');

  constructor(
    private readonly clientPortalService: ClientPortalService,
    private readonly clientAuthService: ClientAuthService,
    private readonly ncService: NcService,
    private readonly previousNcPdfService: PreviousNcPdfService,  // 🆕

  ) { }

  /**
   * ═════════════════════════════════════════════════════════
   * AUTHENTICATION ENDPOINTS (Public)
   * ═════════════════════════════════════════════════════════
   */

  /**
   * Invite client to portal
   * @param inviteDto Company ID, email, phone
   * @param req Request object to get user ID
   */
  @Post('invite')
  @UseGuards(JwtAuthGuard)
  @HttpCode(200)
  async inviteClient(
    @Body() inviteDto: InviteClientDto,
    @Req() req: Request,
  ): Promise<any> {
    this.logger.log(`POST /client-portal/invite`);

    try {
      const userId = this.getCurrentUserId(req);
      const result = await this.clientAuthService.inviteClient(
        inviteDto.company_id,
        inviteDto.client_email,
        inviteDto.client_phone || '',
        userId,
      );
      return { success: true, data: result };
    } catch (error) {
      this.logger.error(`❌ Invite failed: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Client login - verify invite token and send OTP
   * @param loginDto Email and invite token
   */
  @Post('login')
  @Public()
  @UseGuards(ThrottlerGuard)
  @HttpCode(200)
  async clientLogin(@Body() loginDto: ClientLoginDto): Promise<any> {
    this.logger.log(`POST /client-portal/login`);

    try {
      const result = await this.clientAuthService.clientLogin(
        loginDto.email || '',
        loginDto.token || '',
      );
      return { success: true, data: result };
    } catch (error) {
      this.logger.error(`❌ Login failed: ${getErrorMessage(error)}`);
      throw error;
    }
  }
  /**
     * Client login with email (Repeat login - no invite token needed)
     */
  @Post('login-email')
  @Public()
  @UseGuards(ThrottlerGuard)
  @HttpCode(200)
  async loginWithEmail(@Body() loginEmailDto: { email: string }): Promise<any> {
    this.logger.log(`POST /client-portal/login-email`);

    try {
      const result = await this.clientAuthService.loginWithEmail(
        loginEmailDto.email,
      );
      return { success: true, data: result };
    } catch (error) {
      this.logger.error(
        `❌ Login email failed: ${getErrorMessage(error)}`,
      );
      throw error;
    }
  }
  /**
   * Verify OTP and get JWT token
   * @param verifyDto Email and OTP code
   */
  @Post('verify-otp')
  @Public()
  @UseGuards(ThrottlerGuard)
  @HttpCode(200)
  async verifyOtp(
    @Body() verifyDto: VerifyOtpDto,
    @Req() req: Request,             // 👈 NEW — needed to get IP + user agent
  ): Promise<any> {
    this.logger.log(`POST /client-portal/verify-otp`);

    try {
      // 👇 NEW — extract IP address and user agent from the HTTP request
      //         so we can log which device/browser/network the login came from.
      const ipAddress =
        (req.headers['x-forwarded-for'] as string)?.split(',')[0]?.trim() ||
        (req.socket as any)?.remoteAddress ||
        null;
      const userAgent = (req.headers['user-agent'] as string) || null;

      const result = await this.clientAuthService.verifyOtp(
        verifyDto.email,
        verifyDto.otp_code,
        (verifyDto as any).device_name,                       // 👈 from mobile app if provided
        (verifyDto as any).device_type ?? 'web',              // 👈 defaults to 'web'
        ipAddress,                                            // 👈 NEW
        userAgent,                                            // 👈 NEW
      );
      return { success: true, data: result };
    } catch (error) {
      this.logger.error(`❌ OTP verification failed: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Resend OTP code
   * @param resendDto Email and invite token
   */
  @Post('resend-otp')
  @Public()
  @UseGuards(ThrottlerGuard)
  @HttpCode(200)
  async resendOtp(@Body() resendDto: ResendOtpDto): Promise<any> {
    this.logger.log(`POST /client-portal/resend-otp`);

    try {
      const result = await this.clientAuthService.resendOtp(
        resendDto.email,
        resendDto.token,
      );
      return { success: true, data: result };
    } catch (error) {
      this.logger.error(`❌ Resend OTP failed: ${getErrorMessage(error)}`);
      throw error;
    }
  }
  /**
 * Refresh access token using stored refresh token (silent re-login)
 * Mobile calls this automatically when access_token is close to/has expired
 */
  @Post('refresh-token')
  @Public()
  @UseGuards(ThrottlerGuard)
  @HttpCode(200)
  async refreshToken(@Body() dto: RefreshClientTokenDto): Promise<any> {
    this.logger.log(`POST /client-portal/refresh-token`);
    try {
      const result = await this.clientAuthService.refreshAccessToken(dto.refresh_token);
      return { success: true, data: result };
    } catch (error) {
      this.logger.error(`❌ Refresh token failed: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Logout - revoke the refresh token so it can no longer be used
   */
  @Post('logout')
  @Public()
  @UseGuards(ThrottlerGuard)
  @HttpCode(200)
  async logout(@Body() dto: ClientLogoutDto): Promise<any> {
    this.logger.log(`POST /client-portal/logout`);
    try {
      const result = await this.clientAuthService.logout(dto.refresh_token);
      return { success: true, data: result };
    } catch (error) {
      this.logger.error(`❌ Logout failed: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * ═════════════════════════════════════════════════════════
   * DASHBOARD ENDPOINTS (Protected - Require JWT)
   * ═════════════════════════════════════════════════════════
   */

  /**
   * Get client's company information
   * @param req Request with JWT payload containing company_ids
   */
  @Get('company')
  @UseGuards(ClientAuthGuard)
  @HttpCode(200)
  async getCompanyInfo(@Req() req: Request): Promise<any> {
    this.logger.log(`GET /client-portal/company`);

    try {
      const companyIds = this.getCompanyIds(req);
      if (!companyIds || companyIds.length === 0) {
        throw new BadRequestException('No company access found');
      }

      const companyId = companyIds[0];
      const result = await this.clientPortalService.getCompanyInfo(companyId);
      return { success: true, data: result };
    } catch (error) {
      this.logger.error(`❌ Get company failed: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Get company branches
   * @param req Request with JWT payload
   */
  @Get('branches')
  @UseGuards(ClientAuthGuard)
  @HttpCode(200)
  async getBranches(@Req() req: Request): Promise<any> {
    this.logger.log(`GET /client-portal/branches`);

    try {
      const companyIds = this.getCompanyIds(req);
      if (!companyIds || companyIds.length === 0) {
        throw new BadRequestException('No company access found');
      }

      const companyId = companyIds[0];
      const result = await this.clientPortalService.getBranches(companyId);
      return { success: true, data: result };
    } catch (error) {
      this.logger.error(`❌ Get branches failed: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Get audit requests for company
   * @param req Request with JWT payload
   * @param query Pagination and filter options
   */
  @Get('audits')
  @UseGuards(ClientAuthGuard)
  @HttpCode(200)
  async getAudits(
    @Req() req: Request,
    @Query() query: ListClientAuditsDto,
  ): Promise<any> {
    this.logger.log(
      `GET /client-portal/audits (page=${query.page}, status=${query.status})`,
    );

    try {
      const companyIds = this.getCompanyIds(req);
      if (!companyIds || companyIds.length === 0) {
        throw new BadRequestException('No company access found');
      }

      const companyId = companyIds[0];
      const result = await this.clientPortalService.getAudits(companyId, {
        status: query.status,
        limit: query.limit,
        page: query.page,
        sort_by: query.sort_by,
        sort_order: query.sort_order,
      });
      return { success: true, data: result };
    } catch (error) {
      this.logger.error(`❌ Get audits failed: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Get audit progress timeline
   * @param req Request with JWT payload
   * @param auditId Audit request ID
   */
  @Get('audits/:id/progress')
  @UseGuards(ClientAuthGuard)
  @HttpCode(200)
  async getAuditProgress(
    @Param('id') auditId: string,
    @Req() req: Request,
  ): Promise<any> {
    this.logger.log(`GET /client-portal/audits/${auditId}/progress`);

    try {
      const companyIds = this.getCompanyIds(req);
      if (!companyIds || companyIds.length === 0) {
        throw new BadRequestException('No company access found');
      }

      const companyId = companyIds[0];
      const result = await this.clientPortalService.getAuditProgress(
        parseInt(auditId),
        companyId,
      );
      return { success: true, data: result };
    } catch (error) {
      this.logger.error(`❌ Get audit progress failed: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Get audit report
   * @param req Request with JWT payload
   * @param auditId Audit request ID
   */
  @Get('audits/:id/report')
  @UseGuards(ClientAuthGuard)
  @HttpCode(200)
  async getAuditReport(
    @Param('id') auditId: string,
    @Req() req: Request,
  ): Promise<any> {
    this.logger.log(`GET /client-portal/audits/${auditId}/report`);

    try {
      const companyIds = this.getCompanyIds(req);
      if (!companyIds || companyIds.length === 0) {
        throw new BadRequestException('No company access found');
      }

      const companyId = companyIds[0];
      const result = await this.clientPortalService.getAuditReport(
        parseInt(auditId),
        companyId,
      );
      return { success: true, data: result };
    } catch (error) {
      this.logger.error(`❌ Get audit report failed: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Get certificates
   * @param req Request with JWT payload
   */
  @Get('certificates')
  @UseGuards(ClientAuthGuard)
  async getCertificates(@Req() req: Request): Promise<any> {
    try {
      const companyIds = this.getCompanyIds(req);

      if (!companyIds || companyIds.length === 0) {
        throw new BadRequestException('No company access found');
      }

      const companyId = companyIds[0];
      const result = await this.clientPortalService.getCertificates(companyId);

      return { success: true, data: result };
    } catch (error) {
      console.error('❌ Error:', error);
      throw error;
    }
  }
  @Get('ncs')
  @UseGuards(ClientAuthGuard)
  @HttpCode(200)
  async getNcs(@Req() req: Request): Promise<any> {
    try {
      const companyIds = this.getCompanyIds(req);
      if (!companyIds.length) throw new BadRequestException('No company access found');
      const result = await this.clientPortalService.getNcs(companyIds[0]);
      return { success: true, data: result };
    } catch (error) {
      this.logger.error(`❌ Get NCs failed: ${getErrorMessage(error)}`);
      throw error;
    }
  }
  @Get('ncs/list')
  @UseGuards(ClientAuthGuard)
  @HttpCode(200)
  async getNcsList(@Req() req: Request): Promise<any> {
    const companyIds = this.getCompanyIds(req);
    if (!companyIds.length) throw new BadRequestException('No company access found');
    const data = await this.ncService.getClientNcs(companyIds);
    return { success: true, data };
  }

  @Get('ncs/:id')
  @UseGuards(ClientAuthGuard)
  @HttpCode(200)
  async getNcDetail(@Param('id') id: string, @Req() req: Request): Promise<any> {
    const ncId = parseInt(id, 10);
    if (Number.isNaN(ncId)) throw new BadRequestException('Invalid NC id');
    const companyIds = this.getCompanyIds(req);
    if (!companyIds.length) throw new BadRequestException('No company access found');
    const data = await this.ncService.getClientNcDetail(ncId, companyIds);
    return { success: true, data };
  }

  @Get('ncs/:id/pdf')
  @UseGuards(ClientAuthGuard)
  async downloadNcPdf(
    @Param('id') id: string,
    @Req() req: Request,
    @Res() res: Response,
  ) {
    const ncId = parseInt(id, 10);
    if (Number.isNaN(ncId)) throw new BadRequestException('Invalid NC id');
    const companyIds = this.getCompanyIds(req);
    if (!companyIds.length) throw new BadRequestException('No company access found');

    // SECURITY: verify this NC belongs to the client's company.
    // getClientNcDetail throws NotFoundException if it's not theirs.
    // await this.ncService.getClientNcDetail(ncId, companyIds);
    const nc: any = await this.ncService.getClientNcDetail(ncId, companyIds);


    // reuse the existing PDF engine with source='NEW' (scheme_dbs)
    const creatorId = Number(nc?.created_by) || 1;
    const pdf = await this.previousNcPdfService.generateNcReportPdf('NEW', ncId, creatorId)

    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', `inline; filename="NC-${String(ncId).padStart(5, '0')}.pdf"`);
    res.end(pdf);
  }

  @Post('ncs/:id/entries/:entryId/respond')
  @UseGuards(ClientAuthGuard)
  @UseInterceptors(FilesInterceptor('files', 10))
  async respondToFinding(
    @Param('id') id: string,
    @Param('entryId') entryId: string,
    @Body() body: { corrective_action?: string },
    @UploadedFiles() files: any[],
    @Req() req: Request,
  ) {
    const ncId = parseInt(id, 10);
    const eId = parseInt(entryId, 10);
    if (Number.isNaN(ncId) || Number.isNaN(eId)) throw new BadRequestException('Invalid id');
    const companyIds = this.getCompanyIds(req);
    if (!companyIds.length) throw new BadRequestException('No company access found');
    const data = await this.ncService.clientRespondToFinding(
      ncId, eId, companyIds, body?.corrective_action || '',
      (files || []).map((f) => ({ originalname: f.originalname, buffer: f.buffer })),
    );
    return { success: true, data };
  }

  @Get('ncs/:id/entries/:entryId/evidence')
  @UseGuards(ClientAuthGuard)
  async getFindingEvidence(
    @Param('id') id: string,
    @Param('entryId') entryId: string,
    @Query('index') index: string,
    @Req() req: Request,
    @Res() res: Response,
  ) {
    const ncId = parseInt(id, 10);
    const eId = parseInt(entryId, 10);
    if (Number.isNaN(ncId) || Number.isNaN(eId)) throw new BadRequestException('Invalid id');
    const companyIds = this.getCompanyIds(req);
    if (!companyIds.length) throw new BadRequestException('No company access found');
    const idx = parseInt(index || '0', 10) || 0;
    const f = await this.ncService.openFindingEvidence(ncId, eId, companyIds, idx);
    res.setHeader('Content-Type', f.mime);
    res.setHeader('Content-Length', String(f.size));
    res.setHeader('Content-Disposition', `inline; filename="${encodeURIComponent(f.filename)}"`);
    f.stream.pipe(res);
  }
  /**
   * ═════════════════════════════════════════════════════════
   * HELPER METHODS
   * ═════════════════════════════════════════════════════════
   */

  /**
   * Extract user ID from JWT payload
   */
  private getCurrentUserId(req: Request): number {
    const user = req.user as any;
    return user?.id ?? user?.userId ?? user?.sub;
  }
  // FIND THIS:
  // @Get('status/:companyId')
  // @UseGuards(ClientAuthGuard)
  @Get('status/:companyId')
  @UseGuards(JwtAuthGuard)

  // CHANGE TO:
  @Get('status/:companyId')
  @UseGuards(JwtAuthGuard)
  @HttpCode(200)
  async getPortalStatus(@Param('companyId') companyId: string): Promise<any> {
    try {
      const result = await this.clientAuthService.getPortalStatus(parseInt(companyId));
      console.log(JSON.stringify(Object.keys(localStorage)))
      return { success: true, data: result };
    } catch (error) {
      this.logger.error(`❌ Get portal status failed: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  @Get('team')
  @UseGuards(ClientAuthGuard)
  @HttpCode(200)
  async getTeam(@Req() req: Request): Promise<any> {
    try {
      const companyIds = this.getCompanyIds(req);
      if (!companyIds.length) throw new BadRequestException('No company access found');
      const result = await this.clientAuthService.getTeamMembers(companyIds[0]);
      return { success: true, data: result };
    } catch (error) {
      console.log(JSON.stringify(Object.keys(localStorage)))
      this.logger.error(`❌ Get team failed: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  @Post('team/invite')
  @UseGuards(ClientAuthGuard)
  @HttpCode(200)
  async inviteTeamMember(@Body() body: { email: string }, @Req() req: Request): Promise<any> {
    try {
      const companyIds = this.getCompanyIds(req);
      if (!companyIds.length) throw new BadRequestException('No company access found');
      const userId = this.getCurrentUserId(req);
      const result = await this.clientAuthService.inviteTeamMember(companyIds[0], body.email, userId);
      return { success: true, data: result };
    } catch (error) {
      this.logger.error(`❌ Invite team member failed: ${getErrorMessage(error)}`);
      throw error;
    }
  }
  @Get('audits/:id/report-file')
  @UseGuards(ClientAuthGuard)
  async downloadReportFile(
    @Param('id') auditId: string,
    @Query('stage') stage: string,
    @Req() req: Request,
    @Res() res: Response,
  ): Promise<void> {
    const id = parseInt(auditId, 10);
    if (Number.isNaN(id)) throw new BadRequestException('Invalid audit id');
    const st = stage === '2' ? 2 : 1;

    const companyIds = this.getCompanyIds(req);
    if (!companyIds || companyIds.length === 0) {
      throw new BadRequestException('No company access found');
    }

    // try each of the client's companies until one owns this audit
    let opened: any = null;
    for (const companyId of companyIds) {
      try {
        opened = await this.clientPortalService.openAuditReportFile(id, companyId, st as 1 | 2);
        if (opened) break;
      } catch (e) {
        // not this company's audit / no report — try next
      }
    }
    if (!opened) {
      throw new BadRequestException('Report not available');
    }

    res.setHeader('Content-Type', opened.mime);
    res.setHeader('Content-Length', String(opened.size));
    res.setHeader(
      'Content-Disposition',
      `inline; filename="${encodeURIComponent(opened.filename)}"`,
    );
    opened.stream.pipe(res);
  }
  @Post('ncs/:id/sign')
  @UseGuards(ClientAuthGuard)
  async signNc(
    @Param('id') id: string,
    @Body() body: { signature_img: string; signer_name: string; stamp_img?: string },
    @Req() req: Request,
  ) {
    const ncId = parseInt(id, 10);
    if (Number.isNaN(ncId)) throw new BadRequestException('Invalid NC id');
    const companyIds = this.getCompanyIds(req);
    if (!companyIds.length) throw new BadRequestException('No company access found');
    if (!body?.signature_img || !body?.signer_name) throw new BadRequestException('Signature and name required');

    // verify ownership + get company
    const nc: any = await this.ncService.getClientNcDetail(ncId, companyIds);
    const email = (req as any).user?.email || null;

    const data = await this.ncService.saveClientSignature(ncId, Number(nc.company_id), {
      signer_name: body.signer_name,
      signer_email: email,
      signature_img: body.signature_img,
      stamp_img: body.stamp_img,
    });
    return { success: true, data };
  }
  /**
   * Extract company IDs from JWT payload (array of company IDs this client has access to)
   */
  /**
   * Companies the current request may read.
   *
   * FIX (wrong NCs for wrong company): the JWT carries EVERY company the
   * client is linked to. Most endpoints used companyIds[0], but the NC list,
   * report-file and a few others used the whole array — so a client linked
   * to two companies saw NCs from both. Every endpoint is now scoped to ONE
   * company:
   *   • the company sent in the `X-Company-Id` header, if the client is
   *     linked to it (for a future company switcher), otherwise
   *   • the client's newest company link (same as before, companyIds[0]).
   */
  private getCompanyIds(req: Request): number[] {
    const user = req.user as any;
    const all: number[] = (user?.company_ids ?? []).map((n: any) => Number(n)).filter((n: number) => !Number.isNaN(n));
    if (!all.length) return [];
    const header = Number((req.headers['x-company-id'] as string) ?? NaN);
    const active = !Number.isNaN(header) && all.includes(header) ? header : all[0];
    return [active];
  }
}