import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, In } from 'typeorm';
import * as fs from 'fs';
import * as readline from 'readline';
import * as bcrypt from 'bcrypt';
import { CreateBreachDataDto } from './dto/create-breach-data.dto';
import { BreachedPassword } from './entities/breached-password.entity';

@Injectable()
export class TxtImportService {
  private readonly logger = new Logger(TxtImportService.name);

  constructor(
    @InjectRepository(BreachedPassword)
    private readonly breachDataRepository: Repository<BreachedPassword>,
  ) {}

  private async deleteFile(filePath: string): Promise<void> {
    try {
      await fs.promises.unlink(filePath);
      this.logger.log(`File deleted: ${filePath}`);
    } catch (err) {
      this.logger.error(`Failed to delete file ${filePath}: ${err.message}`);
    }
  }

  async importTxt(filePath: string, batchSize = 500, deleteAfterImport = false): Promise<{ totalProcessed: number; totalSaved: number }> {
    if (!fs.existsSync(filePath)) {
      this.logger.error(`File not found: ${filePath}`);
      throw new Error(`File not found: ${filePath}`);
    }

    let totalProcessed = 0;
    let totalSaved = 0;
    let rowsToProcess: CreateBreachDataDto[] = [];
    const processedPasswords = new Set<string>();

    return new Promise<{ totalProcessed: number; totalSaved: number }>((resolve, reject) => {
      try {
        const readStream = fs.createReadStream(filePath, { highWaterMark: 512 * 1024, encoding: 'utf8' });
        const rl = readline.createInterface({ input: readStream, crlfDelay: Infinity, terminal: false });

        const processBatch = async () => {
          if (rowsToProcess.length === 0) return 0;
          const batchToProcess = [...rowsToProcess];
          rowsToProcess = [];

          try {
            const savedCount = await this.processBatch(batchToProcess);
            this.logger.log(`Processed batch of ${batchToProcess.length} records, saved ${savedCount} records`);
            return savedCount;
          } catch (err) {
            this.logger.error(`Error processing batch: ${err.message}`);
            return 0;
          }
        };

        rl.on('line', async line => {
          if (!line.trim()) return;

          const password = line.trim();
          if (!processedPasswords.has(password)) {
            processedPasswords.add(password);
            const hashedPassword = await bcrypt.hash(password, 8);
            rowsToProcess.push({ password: hashedPassword, source: 'Custom' });
          }

          totalProcessed++;
          if (rowsToProcess.length >= batchSize) {
            readStream.pause();
            totalSaved += await processBatch();
            readStream.resume();
          }
        });

        rl.on('close', async () => {
          if (rowsToProcess.length > 0) {
            totalSaved += await processBatch();
          }
          processedPasswords.clear();
          this.logger.log(`Import completed. Total processed: ${totalProcessed}, Total saved: ${totalSaved}`);

          if (deleteAfterImport) {
            await this.deleteFile(filePath);
          }

          resolve({ totalProcessed, totalSaved });
        });

        rl.on('error', err => {
          this.logger.error(`Error reading file: ${err.message}`);
          reject(err);
        });
      } catch (err) {
        this.logger.error(`Unexpected error: ${err.message}`);
        reject(err);
      }
    });
  }

  private async processBatch(batch: CreateBreachDataDto[]): Promise<number> {
    if (batch.length === 0) return 0;

    try {
      const passwords = batch.map(item => item.password);
      const existingPasswords = new Set<string>();

      const existingRecords = await this.breachDataRepository.find({
        select: ['password'],
        where: { password: In(passwords) },
      });

      existingRecords.forEach(record => existingPasswords.add(record.password));
      const newRecords = batch.filter(item => !existingPasswords.has(item.password));

      if (newRecords.length === 0) return 0;

      // Using insert instead of save for better performance
      await this.breachDataRepository.createQueryBuilder().insert().into(BreachedPassword).values(newRecords).execute();

      this.logger.log(`Saved ${newRecords.length} new records`);
      return newRecords.length;
    } catch (err) {
      this.logger.error(`Error saving batch: ${err.message}`);
      return 0;
    }
  }
}
