import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, DataSource } from 'typeorm';
import * as fs from 'fs';
import * as path from 'path';
import * as glob from 'glob';
import { BreachedRecordIdentifier } from '../have-i-been-pawned/entities/identifier.entity';
import { BreachRecord } from '../have-i-been-pawned/entities/breach-data.entity';

interface HospitalPatientRecord {
  id: number;
  health_facility: string;
  fiscal_year: string;
  reg_no: number;
  reg_date: string;
  patient_name: string;
  patient_surname: string;
  caste_code: string;
  sex: string;
  age: number;
  weight: number | null;
  patient_district: string;
  patient_localbody: string;
  ward_no: number;
  contact: string | null;
  [key: string]: any; // For any additional fields
}

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

  constructor(
    @InjectRepository(BreachedRecordIdentifier)
    private identifierRepository: Repository<BreachedRecordIdentifier>,
    @InjectRepository(BreachRecord)
    private breachRepository: Repository<BreachRecord>,
    private dataSource: DataSource,
  ) { }

  async importJsonData(
    directory: string,
    pattern: string = '**/*.json',
    batchSize: number = 1000,
    defaultSource: string = 'Kaju Hospital Records Leaks 2025-May 169K Data"'
  ): Promise<void> {
    if (!this.dataSource.isInitialized) {
      await this.dataSource.initialize();
    }

    try {
      // Find all JSON files using the glob pattern
      const filePaths = glob.sync(path.join(directory, pattern));
      this.logger.log(`Found ${filePaths.length} JSON files to process`);

      // Process files in batches
      for (let i = 0; i < filePaths.length; i += batchSize) {
        const batch = filePaths.slice(i, i + batchSize);
        await this.processJsonFileBatch(batch, defaultSource);
        this.logger.log(`Processed batch ${i / batchSize + 1}: ${batch.length} files`);
      }
    } catch (error) {
      this.logger.error(`JSON import failed: ${error.message}`);
      throw error;
    }
  }

  private async processJsonFileBatch(filePaths: string[], source: string): Promise<void> {
    const records = [];
    let processedFiles = 0;

    for (const filePath of filePaths) {
      try {
        const fileContent = fs.readFileSync(filePath, 'utf8');
        const jsonData: HospitalPatientRecord = JSON.parse(fileContent);

        // Only process records with valid contact information
        if (jsonData.contact) {
          const data = this.extractDataFromJson(jsonData, source, path.basename(filePath));
          if (data.phoneData) {
            records.push(data);
          }
        }

        processedFiles++;
      } catch (error) {
        this.logger.warn(`Failed to process file ${filePath}: ${error.message}`);
      }
    }

    this.logger.log(`Successfully read ${processedFiles} of ${filePaths.length} files`);

    if (records.length > 0) {
      await this.processBatch(records);
      this.logger.log(`Inserted ${records.length} records into database`);
    }
  }

  private async processBatch(records: any[]): Promise<void> {
    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();

    try {
      await queryRunner.startTransaction();
      this.logger.debug("Starting transaction...");

      for (const record of records) {
        if (record.phoneData) {
          await this.upsertIdentifier(
            queryRunner,
            'phone',
            record.phoneData.value,
            record.phoneData.source,
            record.phoneData.year,
            record.phoneData.metadata
          );
        }
      }

      await queryRunner.commitTransaction();
    } catch (error) {
      await queryRunner.rollbackTransaction();
      this.logger.error(`Batch failed: ${error.message}`);
      throw error;
    } finally {
      await queryRunner.release();
    }
  }

  private async upsertIdentifier(
    queryRunner,
    type: 'email' | 'phone',
    value: string,
    source: string,
    year: number | null,
    metadata: any
  ) {
    try {
      const manager = queryRunner.manager;

      // Find or create the identifier
      let identifier = await manager.findOne(BreachedRecordIdentifier, {
        where: { type, value }
      });

      if (!identifier) {
        identifier = manager.create(BreachedRecordIdentifier, { type, value });
        await manager.save(identifier);
      }

      // Check for existing breach record for this identifier
      let breachRecord = await manager.findOne(BreachRecord, {
        where: { identifier: { id: identifier.id } }
      });

      if (breachRecord) {
        // Update source array if new source is not present
        if (!breachRecord.source.includes(source)) {
          breachRecord.source = [...breachRecord.source, source];
        }
        // Update breachedYear array if new year is not present
        if (year !== null && !breachRecord.breachedYear.includes(year)) {
          breachRecord.breachedYear = [...breachRecord.breachedYear, year];
        }
        await manager.save(breachRecord);
      } else {
        // Create new breach record with initial arrays
        breachRecord = manager.create(BreachRecord, {
          identifier,
          source: [source],
          breachedYear: year ? [year] : [],
          metadata
        });
        await manager.save(breachRecord);
      }

      this.logger.debug(`Upserted ${type} ${value} with source ${source}`);
    } catch (error) {
      this.logger.error(`Failed to upsert ${type} ${value}: ${error.message}`);
      throw error;
    }
  }

  /**
   * Extract phone and metadata from JSON record
   */
  private extractDataFromJson(
    jsonData: HospitalPatientRecord,
    source: string,
    filename: string
  ): { phoneData?: { value: string, year: number | null, source: string, metadata: Record<string, any> } } {
    const result: any = {};

    // Skip if jsonData is empty or doesn't have contact info
    if (!jsonData || !jsonData.contact) {
      return result;
    }

    // Determine breach year from fiscal_year or reg_date if possible
    let breachedYear: number = 2025;

    // Create metadata with all fields from the jsonData
    const metadata: Record<string, any> = {
      health_facility: jsonData.health_facility,
      fiscal_year: jsonData.fiscal_year,
      reg_no: jsonData.reg_no,
      reg_date: jsonData.reg_date,
      patient_name: jsonData.patient_name,
      patient_surname: jsonData.patient_surname,
      caste_code: jsonData.caste_code,
      sex: jsonData.sex,
      age: jsonData.age,
      weight: jsonData.weight,
      patient_district: jsonData.patient_district,
      patient_localbody: jsonData.patient_localbody,
      ward_no: jsonData.ward_no
    };
    delete metadata.contact; // Remove contact to avoid duplication

    // Add source file information
    metadata.source_file = "Kaju Hospital Records Leaks 2025-May 169K Data";

    // Clean phone number
    const phoneValue = jsonData.contact;
    if (phoneValue) {
      const cleanedPhone = this.cleanPhoneNumber(phoneValue);

      result.phoneData = {
        value: cleanedPhone,
        year: breachedYear,
        source: [source],
        metadata: metadata
      };
    }
    return result;
  }

  /**
   * Clean phone number by removing common prefixes and non-numeric characters
   */
  private cleanPhoneNumber(phone: string): string {
    if (!phone) return phone;

    // Handle common prefixes
    if (phone.startsWith('+977')) {
      return phone; // Keep the international format
    }

    // Normalize the phone number format
    let cleaned = phone.trim();
    if (!cleaned.startsWith('+') && /^\d+$/.test(cleaned.replace(/\D/g, ''))) {
      const digitsOnly = cleaned.replace(/\D/g, '');
      if (digitsOnly.length === 10) {
        cleaned = digitsOnly; // Keep as 10 digits
      }
    }

    return cleaned;
  }
}