import urllib.request, urllib.parse, json, os
from collections import defaultdict

APP_ID = "fabb3421-8b34-484b-bc17-e46de9703418"
TENANT_ID = "dd4a82e8-85a3-44ac-8800-07945ab4d95f"
CLIENT_SECRET = os.environ["CLIENT_SECRET"]
USER_ID = "41d14430-feb4-4ae2-aed6-2bd4e6384ca7"

# Get token
token_data = urllib.parse.urlencode({
    'client_id': APP_ID,
    'client_secret': CLIENT_SECRET,
    'scope': 'https://graph.microsoft.com/.default',
    'grant_type': 'client_credentials'
}).encode()
req = urllib.request.Request(f"https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token", data=token_data, method='POST')
with urllib.request.urlopen(req) as r:
    token = json.loads(r.read())['access_token']

base = f'https://graph.microsoft.com/v1.0/users/{USER_ID}/contacts'

def patch_contact(cid, data):
    body = json.dumps(data).encode()
    req = urllib.request.Request(f'{base}/{cid}', data=body, method='PATCH',
        headers={'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'})
    with urllib.request.urlopen(req) as r:
        return r.status

def delete_contact(cid):
    req = urllib.request.Request(f'{base}/{cid}', method='DELETE',
        headers={'Authorization': f'Bearer {token}'})
    with urllib.request.urlopen(req) as r:
        return r.status

# Fetch all contacts
url = f'{base}?$select=id,displayName,emailAddresses,companyName,businessPhones,mobilePhone,jobTitle,givenName,surname&$orderby=displayName&$top=999'
all_contacts = []
while url:
    req = urllib.request.Request(url, headers={'Authorization': f'Bearer {token}'})
    with urllib.request.urlopen(req) as r:
        data = json.loads(r.read())
    all_contacts.extend(data.get('value', []))
    url = data.get('@odata.nextLink')
print(f'Total contacts: {len(all_contacts)}')

by_name = defaultdict(list)
for c in all_contacts:
    name = c.get('displayName', '').strip()
    if name:
        by_name[name].append(c)

dupes = {k: v for k, v in by_name.items() if len(v) > 1}
print(f'Duplicate groups: {len(dupes)}')

def merge_emails(keeper, donor):
    keeper_emails = set(e.get('address', '').lower() for e in keeper.get('emailAddresses', []) if e.get('address', '').strip())
    new_emails = [e for e in keeper.get('emailAddresses', []) if e.get('address', '').strip()]
    added = []
    for e in donor.get('emailAddresses', []):
        addr = e.get('address', '')
        if addr.strip() and addr.lower() not in keeper_emails:
            new_emails.append(e)
            added.append(addr)
    return new_emails, added

def merge_phones(keeper, donor):
    def normalize(p):
        return ''.join(c for c in p if c.isdigit())[-10:]
    keeper_phones = set()
    for p in (keeper.get('businessPhones') or []):
        keeper_phones.add(normalize(p))
    if keeper.get('mobilePhone'):
        keeper_phones.add(normalize(keeper['mobilePhone']))
    new_phones = []
    for p in (donor.get('businessPhones') or []):
        if normalize(p) not in keeper_phones:
            new_phones.append(p)
    if donor.get('mobilePhone') and normalize(donor['mobilePhone']) not in keeper_phones:
        new_phones.append(donor['mobilePhone'])
    return new_phones

def do_merge(name, keeper, donor):
    new_emails, added_emails = merge_emails(keeper, donor)
    new_phones = merge_phones(keeper, donor)
    patch = {}
    if added_emails:
        patch['emailAddresses'] = new_emails
    if new_phones:
        biz = list(keeper.get('businessPhones') or []) + new_phones
        patch['businessPhones'] = biz
    if not keeper.get('companyName') and donor.get('companyName'):
        patch['companyName'] = donor['companyName']
    if not keeper.get('jobTitle') and donor.get('jobTitle'):
        patch['jobTitle'] = donor['jobTitle']
    if patch:
        status = patch_contact(keeper['id'], patch)
        extras = []
        if added_emails: extras.append(f"emails: {added_emails}")
        if new_phones: extras.append(f"phones: {new_phones}")
        if 'companyName' in patch: extras.append(f"company: {patch['companyName']}")
        if 'jobTitle' in patch: extras.append(f"job: {patch['jobTitle']}")
        print(f'  [OK] {name}: merged {", ".join(extras)} (status {status})')
    else:
        print(f'  [OK] {name}: no new data to merge')
    del_status = delete_contact(donor['id'])
    print(f'       Deleted duplicate (status {del_status})')

# === EXACT DUPLICATES ===
print('\n--- EXACT DUPLICATES ---')
for name in ['Bardach, Mike', 'Brandon Lopez', 'Judi Carroll', 'Kelly Yang', 'Megan Carroll', 'Winter Williams']:
    contacts = dupes[name]
    for c in contacts[1:]:
        try:
            status = delete_contact(c['id'])
            print(f'  [OK] Deleted: {name} (status {status})')
        except Exception as e:
            print(f'  [ERROR] {name}: {e}')

# === PATSY SABLE (3 copies) ===
print('\n--- Patsy Sable (3 copies) ---')
patsy = dupes['Patsy Sable']
patsy_personal = [c for c in patsy if any(e.get('address', '') == 'patsy@patsysable.com' for e in c.get('emailAddresses', []))]
patsy_work = [c for c in patsy if any(e.get('address', '') == 'psable@longrealty.com' for e in c.get('emailAddresses', []))]
if len(patsy_work) >= 2:
    try:
        status = delete_contact(patsy_work[1]['id'])
        print(f'  [OK] Deleted exact work dupe (status {status})')
    except Exception as e:
        print(f'  [ERROR] work dupe: {e}')
if patsy_personal and patsy_work:
    try:
        do_merge('Patsy Sable', patsy_personal[0], patsy_work[0])
    except Exception as e:
        print(f'  [ERROR] merge: {e}')

# === MERGE PAIRS ===
print('\n--- MERGE PAIRS ---')
for name in ['Barbara Bardach', 'David Rodriguez', 'Denise Newton', 'Gina Beltran',
             'Jessica Bonn', 'Kayla Manley', 'Maria Anemone', 'Mark Crager',
             'Paula Williams', 'Randy Bonn', 'Susan Barry']:
    contacts = dupes[name]
    try:
        do_merge(name, contacts[0], contacts[1])
    except Exception as e:
        print(f'  [ERROR] {name}: {e}')

print('\n=== ALL DONE ===')
