import pool from "../config/db";
import { RowDataPacket } from "mysql2";

/**
 * DeduplicationService — Detecta propiedades duplicadas entre portales.
 *
 * Criterios de duplicado (todos deben cumplirse):
 *  1. Misma municipalidad
 *  2. Precio dentro del ±7%
 *  3. Misma superficie (±5 m²) si disponible
 *  4. Título similar (distancia Levenshtein / palabras comunes ≥60%)
 *
 * Cuando se detecta un duplicado, se marca el más reciente como duplicado
 * apuntando al más antiguo (que tiene el ID más bajo).
 */
export class DeduplicationService {
    async runDeduplication(): Promise<{ duplicatesFound: number }> {
        const [rows] = await pool.query<RowDataPacket[]>(
            `SELECT id, title, price, size_m2, municipality, source, url
       FROM properties WHERE is_duplicate = 0 OR is_duplicate IS NULL
       ORDER BY id ASC`
        );

        const props = rows as Array<{
            id: number;
            title: string;
            price: number;
            size_m2: number;
            municipality: string;
            source: string;
            url: string;
        }>;

        let duplicatesFound = 0;

        for (let i = 0; i < props.length; i++) {
            for (let j = i + 1; j < props.length; j++) {
                const a = props[i];
                const b = props[j];

                // Saltar si ya está marcado como duplicado
                if ((b as any).__isDupe) continue;

                // 1. Mismo municipio
                if (!this.sameMunicipio(a.municipality, b.municipality)) continue;

                // 2. Mismo portal → no es duplicado entre portales (puede ser la misma anuncio en misma web)
                //    Sí se marcan duplicados del mismo portal si precio y tamaño coinciden exacto
                const samePortal = a.source === b.source;

                // 3. Precio similar (±7%)
                if (!this.priceSimilar(a.price, b.price, samePortal ? 0.01 : 0.07)) continue;

                // 4. Tamaño similar (si disponible)
                if (a.size_m2 > 0 && b.size_m2 > 0) {
                    if (Math.abs(a.size_m2 - b.size_m2) > 8) continue;
                }

                // 5. Título similar
                if (!this.titleSimilar(a.title, b.title, 0.5)) continue;

                // Marcar b como duplicado de a
                await pool.query(
                    `UPDATE properties SET is_duplicate = 1, duplicate_of = ? WHERE id = ?`,
                    [a.id, b.id]
                );
                (b as any).__isDupe = true;
                duplicatesFound++;
                console.log(`[Dedup] Duplicado: #${b.id} "${b.title.slice(0, 40)}" (${b.source}) → original #${a.id} (${a.source})`);
            }
        }

        console.log(`[DeduplicationService] ${duplicatesFound} duplicados marcados`);
        return { duplicatesFound };
    }

    /** Desmarca todos los duplicados antes de un nuevo pase */
    async resetDuplicates(): Promise<void> {
        await pool.query(`UPDATE properties SET is_duplicate = 0, duplicate_of = NULL`);
    }

    private sameMunicipio(a: string, b: string): boolean {
        return (a || "").toLowerCase().trim() === (b || "").toLowerCase().trim();
    }

    private priceSimilar(a: number, b: number, tolerance = 0.07): boolean {
        if (!a || !b) return false;
        return Math.abs(a - b) / Math.max(a, b) <= tolerance;
    }

    /**
     * Similitud de título basada en palabras en común (Jaccard simplificado).
     * Umbral: ratio de palabras comunes >= threshold.
     */
    private titleSimilar(a: string, b: string, threshold = 0.5): boolean {
        const tokenize = (s: string) =>
            s.toLowerCase()
                .replace(/[^\wáéíóúñü\s]/gi, "")
                .split(/\s+/)
                .filter(w => w.length > 3); // ignorar palabras cortas

        const wa = new Set(tokenize(a));
        const wb = new Set(tokenize(b));
        const intersection = [...wa].filter(w => wb.has(w)).length;
        const union = new Set([...wa, ...wb]).size;
        if (union === 0) return false;
        return intersection / union >= threshold;
    }
}
