import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
// import { BreachData, BreachEmail } from '../txt-import/entities/breach-data.entity';
import { BreachedPassword } from '../txt-import/entities/breached-password.entity';
import { BreachEmail, Email } from '../txt-import/entities/breached-email.entity';
import * as bcrypt from 'bcrypt';
import { BreachedRecordIdentifier } from './entities/identifier.entity';
import { BreachDto, EmailBreachResponseDto } from './dto/breach-res.dto';
import { BreachRecord } from './entities/breach-data.entity';

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

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

    @InjectRepository(BreachEmail)
    private readonly breachEmailRepository: Repository<BreachEmail>,

    @InjectRepository(BreachedRecordIdentifier)
    private readonly identifierRepository: Repository<BreachedRecordIdentifier>,

    @InjectRepository(Email)
    private readonly emailRepository: Repository<Email>,

    @InjectRepository(BreachRecord)
    private breachRepository: Repository<BreachRecord>,
  ) { }

  /**
   * Check if an email exists in the breach database
   * @param email The email to check
   * @returns Information about breaches containing this email
   */
async checkEmailBreaches(email: string): Promise<EmailBreachResponseDto> {
  try {
    const sanitizedEmail = email.toLowerCase().trim();
    
    // First check in database
    const databaseResult = await this.checkEmailInDatabase(sanitizedEmail);
    
    // Check in external API
    const apiResult = await this.checkEmailInExternalApi(sanitizedEmail);
    
    // Create a combined result
    const combinedResult: EmailBreachResponseDto = {
      found: databaseResult.found || apiResult.found,
      breaches: []
    };
    
    // Add all database breaches
    if (databaseResult.found && databaseResult.breaches.length > 0) {
      combinedResult.breaches = [...databaseResult.breaches];
    }
    
    // Add API breaches (avoiding duplicates)
    if (apiResult.found && apiResult.breaches.length > 0) {
      const existingSources = new Set(combinedResult.breaches.map(breach => breach.source));
      
      apiResult.breaches.forEach(breach => {
        if (!existingSources.has(breach.source)) {
          combinedResult.breaches.push(breach);
        }
      });
      
      // If API found breaches, save them to DB
      const sourcesToSave = apiResult.breaches.map(breach => breach.source);
      try {
        
        await this.updateBreachesInDatabase(sanitizedEmail, sourcesToSave);
      } catch (error) {
        this.logger.error(`Error saving breaches to database: ${error.message}`);
      }
    }
    
    return combinedResult;
  } catch (error) {
    this.logger.error(`Error checking email breaches: ${error.message}`);
    return {
      found: false,
      breaches: []
    };
  }
}


// Database check method - adapted for your specific entity structure
private async checkEmailInDatabase(email: string): Promise<EmailBreachResponseDto> {
  try {
    // Find the identifier entity for this email
    const identifier = await this.identifierRepository.findOne({
      where: { 
        type: 'email',
        value: email 
      },
      relations: ['breaches']
    });
    
    this.logger.log("Identifier found:", identifier);
    
    if (!identifier || !identifier.breaches || identifier.breaches.length === 0) {
      return {
        found: false,
        breaches: []
      };
    }

    // Extract all breach sources from the breach records
    const breaches: BreachDto[] = [];
    
    for (const breachRecord of identifier.breaches) {
      // Handle both string and array formats for sources
      if (breachRecord.source) {
        if (Array.isArray(breachRecord.source) && breachRecord.source.length > 0) {
          // If source is an array, add each source as a separate breach
          for (const sourceName of breachRecord.source) {
            breaches.push({
              id: crypto.randomUUID(),
              source: sourceName,
              date: breachRecord.updatedAt?.toISOString() || new Date().toISOString()
            });
          }
        } else if (typeof breachRecord.source === 'string') {
          // If source is a string, add it directly
          breaches.push({
            id: crypto.randomUUID(),
            source: breachRecord.source,
            date: breachRecord.updatedAt?.toISOString() || new Date().toISOString()
          });
        }
      }
    }

    return {
      found: breaches.length > 0,
      breaches: breaches
    };
  } catch (error) {
    this.logger.error(`Error checking email in database: ${error.message}`);
    return {
      found: false,
      breaches: []
    };
  }
}

// API check method - returns the API response directly
private async checkEmailInExternalApi(email: string): Promise<EmailBreachResponseDto> {
  try {
    const apiUrl = `https://api.xposedornot.com/v1/check-email/${encodeURIComponent(email)}`;
    const response = await fetch(apiUrl);
    
    this.logger.log(`API responded with status: ${response.status}`);

    if (response.status === 404) {
      return {
        found: false,
        breaches: []
      };
    }

    if (!response.ok) {
      throw new Error(`API responded with status: ${response.status}`);
    }

    const data = await response.json();

    if (data.Error === 'Not found') {
      return {
        found: false,
        breaches: []
      };
    }

    const formattedBreaches: BreachDto[] = [];
    
    // Handle the nested array structure in the API response
    if (data.breaches && Array.isArray(data.breaches) && data.breaches.length > 0) {
      // The first element appears to be an array of breach sources
      const sourcesArray = data.breaches[0];
      
      if (Array.isArray(sourcesArray)) {
        // Each item in the array is a source name
        sourcesArray.forEach(source => {
          if (source) {
            formattedBreaches.push({
              id: crypto.randomUUID(),
              source: source,
              date: new Date().toISOString()
            });
          }
        });
      }
    }

    return {
      found: formattedBreaches.length > 0,
      breaches: formattedBreaches
    };
  } catch (error) {
    this.logger.error(`Error checking email in external API: ${error.message}`);
    return {
      found: false,
      breaches: []
    };
  }
}

// Method to save or update breaches in the database
private async updateBreachesInDatabase(email: string, newSources: string[]): Promise<void> {
  if (!newSources || newSources.length === 0) {
    return;
  }
  
  try {
    // Step 1: Find or create the identifier
    let identifier = await this.identifierRepository.findOne({
      where: { 
        type: 'email',
        value: email 
      },
      relations: ['breaches']
    });
    
    if (!identifier) {
      // Create new identifier if it doesn't exist
      identifier = new BreachedRecordIdentifier();
      identifier.type = 'email';
      identifier.value = email;
      identifier.breaches = [];
      
      // Save the identifier first
      identifier = await this.identifierRepository.save(identifier);
    }
    
    // Ensure breaches array is initialized
    if (!identifier.breaches) {
      identifier.breaches = [];
    }
    
    // Step 2: Handle the breach record
    let breachRecord: BreachRecord;
    
    if (identifier.breaches.length > 0) {
      // Use existing breach record
      breachRecord = identifier.breaches[0];
      
      // Initialize the source array if it doesn't exist or convert string to array
      if (!breachRecord.source) {
        breachRecord.source = [];
      } else if (typeof breachRecord.source === 'string') {
        // Convert string source to array
        breachRecord.source = [breachRecord.source];
      }
      
      // Ensure source is an array
      if (!Array.isArray(breachRecord.source)) {
        breachRecord.source = [];
      }
      
      // Add new sources to existing sources array, avoiding duplicates
      const existingSources = breachRecord.source;
      const combinedSources = [...new Set([...existingSources, ...newSources])];
      breachRecord.source = combinedSources;
      
      // Update breach year if needed
      const currentYear = new Date().getFullYear();
      
      // Fix: ensure breachedYear is an array before calling includes()
      if (!breachRecord.breachedYear) {
        breachRecord.breachedYear = [];
      }
      
      // Make sure breachedYear is always an array
      if (!Array.isArray(breachRecord.breachedYear)) {
        breachRecord.breachedYear = breachRecord.breachedYear ? [breachRecord.breachedYear] : [];
      }
      
      if (!breachRecord.breachedYear.includes(currentYear)) {
        breachRecord.breachedYear.push(currentYear);
      }
    } else {
      // Create a new breach record with all required fields explicitly set
      breachRecord = new BreachRecord();
      breachRecord.identifier = identifier;
      breachRecord.source = newSources;
      breachRecord.breachedYear = [new Date().getFullYear()];
      breachRecord.metadata = {}; // Initialize with empty object if it's required
      
      // Add to identifier's breaches array
      identifier.breaches.push(breachRecord);
    }
    
    // Step 3: Save the breach record AND update the identifier relation
    await this.breachRepository.save(breachRecord);
    
    // Step 4: Save the identifier with its updated breaches collection
    await this.identifierRepository.save(identifier);
    
    this.logger.log(`Updated breaches for ${email} with sources: ${newSources.join(', ')}`);
  } catch (error) {
    this.logger.error(`Error updating breaches in database: ${error.message}`);
    throw error;
  }
}
  /**
   * Check if a password exists in the breach database
   * @param password The password to check
   * @returns Information about breaches containing this password
   */

  async checkPassword(password: string): Promise<{ found: boolean; count: number }> {
    try {
      this.logger.log(`Checking breach data for password: ${password.substring(0, 5)}***`);

      // Hash the password first
      const hashedPassword = await bcrypt.hash(password, 4); // 10 is the salt rounds

      this.logger.log(`Hashed password: ${hashedPassword}`);
      // Query the database directly for the hashed password
      const breachedPassword = await this.breachDataRepository
        .createQueryBuilder('breachPassword')
        .where('breachPassword.password = :password', { password: hashedPassword })
        .getOne(); // Use getOne() to fetch a single result

      if (breachedPassword) {
        this.logger.warn(`Password found in breach database: ${password.substring(0, 5)}***`);
        return { found: true, count: 1 }; // Found a match
      }

      return { found: false, count: 0 }; // No match found
    } catch (error) {
      this.logger.error(`Error checking password in breach database: ${error.message}`);
      throw new NotFoundException('Error occurred while checking password');
    }
  }


  /**
   * Check if a phone number exists in the breach database
   * @param phone The phone number to check
   * @returns Information about breaches containing this phone number
   */
  async checkPhone(phone: string): Promise<{ found: boolean; breaches: any[] }> {
    try {
      // Normalize the phone number
      const normalizedPhone = phone.startsWith('+')
        ? '+' + phone.substring(1).replace(/[^0-9]/g, '')
        : phone.replace(/[^0-9]/g, '');


      // Find the phone identifier using the repository
      const phoneIdentifier = await this.identifierRepository.findOne({
        where: {
          type: 'phone',
          value: normalizedPhone
        },
        relations: ['breaches']
      });

      if (!phoneIdentifier || !phoneIdentifier.breaches || phoneIdentifier.breaches.length === 0) {
        return {
          found: false,
          breaches: [],
        };
      }

      // Format the breaches data
      const breaches = phoneIdentifier.breaches.map(breach => ({
        id: breach.id,
        source: breach.source,
        date: breach.createdAt?.toISOString() || new Date().toISOString(),
      }));

      return {
        found: true,
        breaches: breaches,
      };
    } catch (error) {
      this.logger.error(`Error checking phone in breach database: ${error.message}`);
      return {
        found: false,
        breaches: [],
      };
    }
  }


  /**
   * Gets stats about the breach database
   * @returns Statistics about the breach database
   */
  async getBreachStats(): Promise<{ totalRecords: number; sources: { source: string; count: number }[] }> {
    try {
      const totalRecords = await this.breachDataRepository.count();

      const sourcesResult = await this.breachDataRepository
        .createQueryBuilder('breach')
        .select('breach.source', 'source')
        .addSelect('COUNT(*)', 'count')
        .groupBy('breach.source')
        .orderBy('count', 'DESC')
        .limit(10)
        .getRawMany();

      return {
        totalRecords,
        sources: sourcesResult.map(item => ({
          source: item.source,
          count: parseInt(item.count),
        })),
      };
    } catch (error) {
      this.logger.error(`Error retrieving breach statistics: ${error.message}`);
      throw new NotFoundException('Breach statistics not available');
    }
  }

}


