import { Injectable, NotFoundException, Logger, InternalServerErrorException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Report } from './entity/report.entity';
import { QuestionResponse } from './entity/ques-res.entity';
import { CreateReportDto, DemographicInfoDto, QuestionResponseDto } from './dto/report.dto';
import * as nodemailer from 'nodemailer';
import { ConfigService } from '@nestjs/config';
import { TicketsService } from 'modules/ticket/ticket.service';
import { CreateTicketDto } from 'modules/ticket/dto/create-ticket.dto';
import { TicketPriority, TicketSource, TicketStatus } from 'common/enum/ticket.enum';
import { ReportResponseDto } from './dto/report-response.dto';
import { CreateCommentDto } from 'modules/ticket/dto/create-comment.dto';
import { TicketResponseDto } from 'modules/ticket/dto/response-ticket.dto';
import { UserRole } from 'common/enum/UserRole.enum';
import { Comment } from 'modules/ticket/entities/comment.entity';
import { AlertService } from 'modules/alert/alert.service';
import { CreateAlertDto } from 'modules/alert/dto/create-alert.dto';
import { AttackType, AlertType } from 'common/enum/alert.enum';
import { generateFilledFinancialFraduForm } from './pdf-forms/financial-fraud-form';
import { CloudinaryService } from 'config/multer/cloudinary.service';
import { generateFilledKarbahiForm } from './pdf-forms/other-form';
import { generateFilledFakeOrHackedForm } from './pdf-forms/hacked-complain';
import { mapToFinancialFraudForm, mapToFakeOrHackedForm, mapToKarbahiForm } from './utils/mapResponseToForm';


@Injectable()
export class ReportService {
  private readonly logger = new Logger(ReportService.name);
  private readonly transporter: nodemailer.Transporter;

  constructor(
    @InjectRepository(Report)
    private reportRepository: Repository<Report>,
    @InjectRepository(QuestionResponse)
    private responseRepository: Repository<QuestionResponse>,
    private readonly configService: ConfigService,
    private readonly ticketService: TicketsService,
    private readonly alertService: AlertService,
    private readonly cloudinaryService: CloudinaryService
  ) {
    this.transporter = nodemailer.createTransport({
      host: this.configService.get<string>('CONTACT_SMTP_HOST'),
      port: this.configService.get<number>('CONTACT_SMTP_PORT'),
      secure: true,
      auth: {
        user: this.configService.get<string>('CONTACT_SMTP_USER'),
        pass: this.configService.get<string>('CONTACT_SMTP_PASSWORD'),
      },
    });
  }

 async create(createReportDto: CreateReportDto): Promise<ReportResponseDto> {
  try {
    const { demographicInfo, responses, userDetails, questionType } = createReportDto;

    const report = new Report();
    report.gender = demographicInfo.gender;
    report.age = demographicInfo.age.toString();
    report.state = demographicInfo.state;
    report.district = demographicInfo.district;
    report.questionType = questionType;
    
    if (userDetails?.id) {
      report.userId = Number(userDetails.id);
    }

    const savedReport = await this.reportRepository.save(report);

    const questionResponses = responses.map(response => {
      const questionResponse = new QuestionResponse();
      questionResponse.questionId = response.questionId;
      questionResponse.questionText = response.questionText;
      questionResponse.answer = Array.isArray(response.answer) ? response.answer : [response.answer];
      questionResponse.report = savedReport;
      return questionResponse;
    });

    await this.responseRepository.save(questionResponses);

    // Prepare ticket description
    let description = `Report submitted from ${report.district}, ${report.state}\n\nDemographic Information:\n- Gender: ${report.gender}\n- Age: ${report.age}\n\n`;
    if (report.userId) description += `- User ID: ${report.userId}\n\n`;
    description += `Responses:\n${questionResponses.map(r => `- ${r.questionText}: ${r.answer}`).join('\n')}`;

    const ticketDto: CreateTicketDto = {
      title: `Report from ${report.district}, ${report.state}`,
      description,
      source: TicketSource.BOT_REPORT,
      labels: ['report', report.state.toLowerCase(), report.district.toLowerCase()],
      priority: TicketPriority.MEDIUM,
      status: TicketStatus.OPEN,
      referenceId: savedReport.id,
      referenceType: 'report',
      assigneeId: this.configService.get<number>('DEFAULT_REPORT_ASSIGNEE_ID', null),
    };

    const ticket = await this.ticketService.create(ticketDto, null, UserRole.USER);
    this.logger.log(`Ticket created: ${ticket.ticketToken} for report ${savedReport.id}`);

    savedReport.ticketId = ticket.id;
    savedReport.isTicketCreated = true;
    savedReport.ticketToken = ticket.ticketToken;
    
    await this.reportRepository.save(savedReport);
    
    const reportResponse = await this.findOne(savedReport.id);
    
    // Background processes
    this.sendReportEmail(savedReport, questionResponses)
      .catch(error => this.logger.error('Email failed:', error));
    this.processPdfInBackground(savedReport, questionType, responses, demographicInfo)
      .catch(error => this.logger.error('PDF processing failed:', error));
    
    return reportResponse;
  } catch (error) {
    this.logger.error(`Create report error: ${error.message}`, error.stack);
    throw new InternalServerErrorException('Failed to create report');
  }
}

  // Separate method to handle PDF processing asynchronously
  private async processPdfInBackground(
    report: Report,
    questionType: string,
    responses: QuestionResponseDto[],
    demographicInfo: DemographicInfoDto
  ): Promise<void> {
    try {
      let pdfBuffer: Buffer;

      // Normalize the questionType string to match the enum values
      const normalizedQuestionType = questionType.toLowerCase().replace(/ /g, '_');

      if (normalizedQuestionType === 'financial_fraud') {
        const formData = mapToFinancialFraudForm(responses, demographicInfo);
        pdfBuffer = await generateFilledFinancialFraduForm(formData);
      }
      else if (normalizedQuestionType === 'hacked_complain') {
        const formData = mapToFakeOrHackedForm(responses, demographicInfo);
        pdfBuffer = await generateFilledFakeOrHackedForm(formData);
      }
      else if (normalizedQuestionType === 'other') {
        const formData = mapToKarbahiForm(responses, demographicInfo);
        pdfBuffer = await generateFilledKarbahiForm(formData);
      }
      else {
        this.logger.warn(`Unknown question type: ${questionType}`);
        return;
      }

      if (pdfBuffer) {
        const uploadResult = await this.cloudinaryService.uploadFile(pdfBuffer as Buffer);

        if (uploadResult && uploadResult.secure_url) {
          report.pdfUrl = uploadResult.secure_url;
          await this.reportRepository.save(report);
          this.logger.log(`PDF generated and uploaded to Cloudinary: ${uploadResult.secure_url}`);
        } else {
          this.logger.warn('PDF was generated but Cloudinary URL is null');
        }
      }
    } catch (pdfError) {
      this.logger.error(`Failed to generate or upload PDF: ${pdfError.message}`, pdfError.stack);
      // We don't throw here as this is processed in the background
    }
  }

  async getPdfByReportId(id: number): Promise<string> {
    const report = await this.reportRepository.findOne({
      where: { id },
    });

    if (!report) {
      throw new NotFoundException(`Report with ID ${id} not found`);
    }

    if (!report.pdfUrl) {
      throw new NotFoundException(`PDF for report with ID ${id} not found`);
    }

    return report.pdfUrl;
  }

  async findByUserId(userId: number): Promise<Report> {
    try {
      const report = await this.reportRepository.findOne({
        where: { userId },
        order: { createdAt: 'DESC' } // Get the most recent report
      });

      if (!report) {
        throw new NotFoundException(`No report found for user with ID ${userId}`);
      }

      return report;
    } catch (error) {
      this.logger.error(`Error finding report by user ID: ${error.message}`, error.stack);
      if (error instanceof NotFoundException) {
        throw error;
      }
      throw new InternalServerErrorException('Failed to find report by user ID');
    }
  }

  async getPdfByTicketToken(ticketToken: string): Promise<string> {
    const report = await this.reportRepository.findOne({
      where: { ticketToken },
    });

    if (!report) {
      throw new NotFoundException(`Report with ticket token ${ticketToken} not found`);
    }

    if (!report.pdfUrl) {
      throw new NotFoundException(`PDF for report with ticket token ${ticketToken} not found`);
    }

    return report.pdfUrl;
  }

  async sendReportEmail(report: Report, responses: QuestionResponse[]): Promise<void> {
    try {
      const emailBody = `
        <h3>New Report Created</h3>
        <p><strong>Gender:</strong> ${report.gender}</p>
        <p><strong>Age:</strong> ${report.age}</p>
        <p><strong>State:</strong> ${report.state}</p>
        <p><strong>District:</strong> ${report.district}</p>
        <h4>Responses:</h4>
        <ul>
          ${responses.map(r => `<li><strong>${r.questionText}:</strong> ${r.answer}</li>`).join('')}
        </ul>
      `;

      const mailOptions = {
        from: this.configService.get<string>('CONTACT_SMTP_USER'),
        to: this.configService.get<string>('REPORT_RECIPIENT_EMAIL'),
        subject: 'New Report Submitted',
        html: emailBody,
      };

      await this.transporter.sendMail(mailOptions);
      this.logger.log(`Report email sent successfully`);
    } catch (error) {
      this.logger.error(`Failed to send report email: ${error.message}`, error.stack);
    }
  }

  async findAll(): Promise<ReportResponseDto[]> {
    const reports = this.reportRepository.find({
      relations: ['responses'],
      order: { createdAt: 'DESC' },
    });
    if (!reports) {
      throw new NotFoundException('No reports found');
    }
    return reports;
  }

  async findOne(id: number): Promise<ReportResponseDto> {
    const report = await this.reportRepository.findOne({
      where: { id },
      relations: ['responses'],
    });

    if (!report) {
      throw new NotFoundException(`Report with ID ${id} not found`);
    }

    return report;
  }

  async addCommentToReportTicket(
    ticketToken: string,
    comment: CreateCommentDto,
    userId: number,
    file?: Express.Multer.File,
  ): Promise<TicketResponseDto> {
    const report = await this.reportRepository.findOne({
      where: { ticketToken },
      relations: ['responses'],
    });

    if (!report) {
      throw new NotFoundException(`Report with TicketToken "${ticketToken}" not found`);
    }

    // Check if the ticket is created
    const ticket = this.ticketService.findByToken(ticketToken, UserRole.USER);

    if (!ticket) {
      throw new NotFoundException(`Ticket with Token "${ticketToken}" not found`);
    }

    // Add comment to the ticket using the ticket service
    return this.ticketService.addComment(report.ticketId, comment, userId, UserRole.USER, file);
  }

  async getCommentsForReportTicket(ticketToken: string): Promise<Comment[]> {
    const report = await this.reportRepository.findOne({
      where: { ticketToken },
      relations: ['responses'],
    });

    if (!report) {
      throw new NotFoundException(`Report with TicketToken "${ticketToken}" not found`);
    }

    // Check if the ticket is created
    const ticket = await this.ticketService.findByToken(ticketToken, UserRole.USER);

    if (!ticket) {
      throw new NotFoundException(`Ticket with Token "${ticketToken}" not found`);
    }

    // Get comments for the ticket using the ticket service
    return this.ticketService.getComments(ticket.id, UserRole.USER);
  }

  async findReportByToken(token: string): Promise<ReportResponseDto> {
    const report = await this.reportRepository.findOne({
      where: { ticketToken: token },
      relations: ['responses'],
    });

    if (!report) {
      throw new NotFoundException(`Report with Token "${token}" not found`);
    }

    return report;
  }

  async findReportTokensByUserId(userId: number): Promise<any[]> {
    try {
      const reports = await this.reportRepository.find({
        where: { userId: userId },
        select: ['id', 'ticketToken', 'createdAt'],
      });

      if (!reports || reports.length === 0) {
        return [];
      }

      return reports
        .filter(report => report.ticketToken !== null)
        .map(report => ({
          id: report.id,
          ticketToken: report.ticketToken,
          description: `Created on ${new Date(report.createdAt).toLocaleDateString()}`,
        }));
    } catch (error) {
      this.logger.error(`Error finding report tokens for user ${userId}: ${error.message}`, error.stack);
      throw new InternalServerErrorException('Failed to retrieve report tokens');
    }
  }

  async markCommentAsSeen(ticketToken: string, commentId: number): Promise<Comment> {
    const report = await this.reportRepository.findOne({
      where: { ticketToken },
      relations: ['responses'],
    });

    if (!report) {
      throw new NotFoundException(`Report with TicketToken "${ticketToken}" not found`);
    }

    // Check if the ticket is created
    const ticket = await this.ticketService.findByToken(ticketToken, UserRole.USER);

    if (!ticket) {
      throw new NotFoundException(`Ticket with Token "${ticketToken}" not found`);
    }

    // Mark comment as seen using the ticket service
    return this.ticketService.markCommentAsSeen(ticket.ticketToken, commentId, true, new Date());
  }


  async submitAnonymousReport(
    data: any,
    files?: Express.Multer.File[]
  ): Promise<any> {
    this.logger.log('Received anonymous threat report');
    try {
      let responses = data.responses;
      if (typeof responses === 'string') {
        responses = JSON.parse(responses);
      }

      const alertData = this.transformToAlertData(responses, files);


      const createdAlert = await this.alertService.createAlert(alertData, alertData.image);

      return {
        success: true,
        message: 'Anonymous threat report submitted successfully',
        alertId: createdAlert.id
      };
    } catch (error) {
      this.logger.error(`Error processing anonymous report: ${error.message}`, error.stack);
      throw new InternalServerErrorException('Failed to process anonymous report');
    }
  }

  private transformToAlertData(responses: any[], files?: Express.Multer.File[]): CreateAlertDto {
    const titleResponse = responses.find(r =>
      r.questionText.toLowerCase().includes('title') ||
      r.questionText.toLowerCase().includes('subject')
    );

    let description = responses.map(r =>
      `${r.questionText}: ${r.answer || 'No answer provided'}`
    ).join('\n\n');

    if (files && files.length > 0) {
      description += '\n\n--- Attached Evidence Files ---\n';
      files.forEach((file, index) => {
        description += `${index + 1}. ${file.originalname} (${Math.round(file.size / 1024)} KB)\n`;
      });
    }

    const questionResponsesDto = responses.map(r => ({
      questionId: r.questionId.toString(),
      questionText: r.questionText,
      answer: r.answer || 'No answer provided'
    }));

    let attackType = AttackType.OTHER;
    for (const response of responses) {
      if (!response.answer) continue;

      const answer = response.answer.toLowerCase();
      if (answer.includes('malware')) {
        attackType = AttackType.MALWARE;
      } else if (answer.includes('ransomware')) {
        attackType = AttackType.RANSOMWARE;
      } else if (answer.includes('phishing')) {
        attackType = AttackType.PHISHING;
      } else if (answer.includes('ddos')) {
        attackType = AttackType.DDOS;
      } else if (answer.includes('data breach')) {
        attackType = AttackType.DATA_BREACH;
      }
    }

    const alertData: CreateAlertDto = {
      title: titleResponse?.answer || 'Anonymous Threat Report',
      description: description,
      questionResponses: questionResponsesDto,
      source: 'Anonymous Report via CyberAlertNepal',
      targetCountry: 'Nepal',
      targetOrganization: 'Various',
      attackType: attackType,
      type: AlertType.MANUAL,
      isRelevantToNepal: true,
      severity: 1,
    };

    // Include image in the alertData if files exist
    if (files && files.length > 0) {
      // Set the first file as the image
      alertData.image = files[0];
    }

    return alertData;
  }


}
