import { Injectable } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';

import { Lead } from '../entities/lead.entity';

export interface DuplicateReason {
  label: string;
  code: string;
}

export interface DuplicateMatch {
  lead: Lead;
  reasons: DuplicateReason[];
  severity: 'high' | 'low';
  hardSignals: number;
  softSignals: number;
}

export interface DuplicateClientMatch {
  client: any;
  reasons: DuplicateReason[];
  severity: 'high' | 'low';
  hardSignals: number;
  softSignals: number;
}

interface DuplicateQuery {
  company?: string | null;
  contact?: string | null;
  phone?: string | null;
  email?: string | null;
}

/**
 * Hard signals (exact match on a unique-ish identifier) => high severity,
 * blocks save unless overridden. Soft signals (fuzzy company-name match)
 * => low severity, informational only. Mirrors the Laravel service's
 * scoring: any hard hit makes the whole match 'high'.
 */
@Injectable()
export class DuplicateLeadDetectorService {
  constructor(
    @InjectDataSource('scheme_dbs')
    private readonly dataSource: DataSource,
  ) {}

  async find(query: DuplicateQuery, ignoreId?: number | null): Promise<DuplicateMatch[]> {
    const qb = this.dataSource
      .getRepository(Lead)
      .createQueryBuilder('l')
      .leftJoinAndSelect('l.assignedUser', 'assignedUser')
      .withDeleted() // detector must see across all reps' leads
      .where('1=1');

    if (ignoreId) {
      qb.andWhere('l.id != :ignoreId', { ignoreId });
    }

    const orConditions: string[] = [];
    const params: Record<string, any> = {};

    if (query.email) {
      orConditions.push('l.email = :email');
      params.email = query.email;
    }
    if (query.phone) {
      orConditions.push('l.phone = :phone');
      params.phone = query.phone;
    }
    if (query.company) {
      orConditions.push('l.company LIKE :companyLike');
      params.companyLike = `%${query.company}%`;
    }
    if (query.contact) {
      orConditions.push('l.contact LIKE :contactLike');
      params.contactLike = `%${query.contact}%`;
    }

    if (orConditions.length === 0) return [];

    qb.andWhere(`(${orConditions.join(' OR ')})`, params).take(20);

    const candidates = await qb.getMany();

    return candidates.map((lead) => this.score(lead, query)).filter((m) => m.reasons.length > 0);
  }

  /**
   * TODO: wire this to your actual Client/Company entity once available.
   * Left as a safe no-op so the create()/import flows don't break — the
   * Laravel version cross-checks an existing `clients` table which isn't
   * present in the Nest project yet.
   */
  async findInClients(_query: DuplicateQuery): Promise<DuplicateClientMatch[]> {
    return [];
  }

  private score(lead: Lead, query: DuplicateQuery): DuplicateMatch {
    const reasons: DuplicateReason[] = [];
    let hardSignals = 0;
    let softSignals = 0;

    if (query.email && lead.email && lead.email.toLowerCase() === query.email.toLowerCase()) {
      reasons.push({ label: 'Exact email match', code: 'email' });
      hardSignals++;
    }
    if (query.phone && lead.phone && this.normalizePhone(lead.phone) === this.normalizePhone(query.phone)) {
      reasons.push({ label: 'Exact phone match', code: 'phone' });
      hardSignals++;
    }
    if (
      query.company &&
      lead.company &&
      lead.company.trim().toLowerCase() === query.company.trim().toLowerCase()
    ) {
      reasons.push({ label: 'Exact company name match', code: 'company_exact' });
      hardSignals++;
    } else if (
      query.company &&
      lead.company &&
      lead.company.toLowerCase().includes(query.company.toLowerCase())
    ) {
      reasons.push({ label: 'Similar company name', code: 'company_fuzzy' });
      softSignals++;
    }
    if (
      query.contact &&
      lead.contact &&
      lead.contact.trim().toLowerCase() === query.contact.trim().toLowerCase()
    ) {
      reasons.push({ label: 'Exact contact name match', code: 'contact_exact' });
      softSignals++;
    }

    return {
      lead,
      reasons,
      severity: hardSignals > 0 ? 'high' : 'low',
      hardSignals,
      softSignals,
    };
  }

  private normalizePhone(phone: string): string {
    return phone.replace(/[^\d]/g, '');
  }
}
