using System; using System.Collections.Generic; using System.Linq; using StarfaceOutlookSync.Models; namespace StarfaceOutlookSync.Services { /// /// Erkennt, ob zwei Kontakte dieselbe Person/Firma sind. Wird sowohl vom /// Sync (Wiederzuordnung, Duplikat-Vermeidung) als auch von der /// Dubletten-Zusammenfuehrung genutzt - beide MUESSEN dieselbe Logik /// verwenden, sonst laufen sie gegeneinander. /// public static class ContactMatcher { public static UnifiedContact FindMatch(UnifiedContact contact, List candidates) { if (candidates == null || candidates.Count == 0) return null; foreach (var c in candidates) if (IsMatch(contact, c)) return c; return null; } public static bool IsMatch(UnifiedContact a, UnifiedContact b) { bool hasName = (!string.IsNullOrEmpty(a.FirstName) || !string.IsNullOrEmpty(a.LastName)) && (!string.IsNullOrEmpty(b.FirstName) || !string.IsNullOrEmpty(b.LastName)); bool emailMatch = !string.IsNullOrEmpty(a.Email) && !string.IsNullOrEmpty(b.Email) && a.Email.Equals(b.Email, StringComparison.OrdinalIgnoreCase); bool nameMatch = hasName && (a.FirstName ?? "").Equals(b.FirstName ?? "", StringComparison.OrdinalIgnoreCase) && (a.LastName ?? "").Equals(b.LastName ?? "", StringComparison.OrdinalIgnoreCase); // Telefon feldUEBERGREIFEND und formatunabhaengig (+49 == 0). bool phoneMatch = SharedPhone(a, b); bool companyMatch = !string.IsNullOrEmpty(a.Company) && !string.IsNullOrEmpty(b.Company) && a.Company.Equals(b.Company, StringComparison.OrdinalIgnoreCase); bool emailContradiction = !string.IsNullOrEmpty(a.Email) && !string.IsNullOrEmpty(b.Email) && !emailMatch; if (emailMatch) return true; if (nameMatch && !emailContradiction) return true; // Firma-/Service-Eintraege OHNE Personennamen: gleiche Firma + gemeinsame // Nummer. Bewusst nur ohne Namen, damit nicht zwei Kollegen mit gleicher // Zentrale-Nummer + Firma faelschlich verschmolzen werden. bool aHasName = !string.IsNullOrEmpty(a.FirstName) || !string.IsNullOrEmpty(a.LastName); bool bHasName = !string.IsNullOrEmpty(b.FirstName) || !string.IsNullOrEmpty(b.LastName); if (phoneMatch && companyMatch && !aHasName && !bHasName && !emailContradiction) return true; // Reine Nummern-Eintraege: eine gemeinsame Rufnummer identifiziert den Eintrag. if (phoneMatch && IsBareNumberEntry(a) && IsBareNumberEntry(b)) return true; return false; } private static bool IsBareNumberEntry(UnifiedContact c) => string.IsNullOrEmpty(c.FirstName) && string.IsNullOrEmpty(c.LastName) && string.IsNullOrEmpty(c.Company) && string.IsNullOrEmpty(c.Email); /// True, wenn a und b mindestens eine (normalisierte) Rufnummer gemeinsam haben. public static bool SharedPhone(UnifiedContact a, UnifiedContact b) { var pa = PhoneSet(a); if (pa.Count == 0) return false; var pb = PhoneSet(b); return pb.Count > 0 && pa.Overlaps(pb); } private static HashSet PhoneSet(UnifiedContact c) { var set = new HashSet(); foreach (var p in new[] { c.PhoneWork, c.PhoneMobile, c.PhoneHome, c.Fax }) { var n = NormalizePhone(p); if (n.Length >= 5) set.Add(n); // zu kurze (z.B. reine Durchwahlen) ignorieren } return set; } public static string NormalizePhone(string phone) { if (string.IsNullOrEmpty(phone)) return ""; var s = new string(phone.Where(c => char.IsDigit(c) || c == '+').ToArray()); if (s.StartsWith("+49")) s = "0" + s.Substring(3); else if (s.StartsWith("0049")) s = "0" + s.Substring(4); else if (s.StartsWith("+")) s = s.Substring(1); return s; } } }