#!/usr/bin/env python3
"""
Match RADIUS PPPoE usernames to Gaiia accounts by exact name-to-username mapping.
Deterministic only — no fuzzy matching. Leaves ambiguous/unmatched rows blank.

A Gaiia displayName like "Jose Ortiz" generates candidate usernames:
  joseortiz, jortiz, joseo, jose, ortiz
The RADIUS username must match one of these exactly.
"""

import csv
import json
import re
import subprocess
import sys
from collections import defaultdict

GAIIA_KEY = "gaiia_sk_live_955b27e3680747a69ad40b403a912155_b0d1be3c"
GAIIA_ENDPOINT = "https://api.gaiia.com/api/v1"


def fetch_all_accounts():
    """Fetch all Gaiia accounts with id and displayName."""
    accounts = []
    cursor = None
    while True:
        after_arg = f', after: "{cursor}"' if cursor else ""
        query = f"""
        query {{
            accounts(first: 250{after_arg}) {{
                nodes {{ id readableId name displayName }}
                pageInfo {{ hasNextPage endCursor }}
            }}
        }}
        """
        result = subprocess.run(
            [
                "curl", "-s",
                "-X", "POST", GAIIA_ENDPOINT,
                "-H", "Content-Type: application/json",
                "-H", f"X-Gaiia-Api-Key: {GAIIA_KEY}",
                "-H", "x-timezone: America/Chicago",
                "-d", json.dumps({"query": query}),
            ],
            capture_output=True, text=True, timeout=30,
        )
        data = json.loads(result.stdout)
        nodes = data["data"]["accounts"]["nodes"]
        for n in nodes:
            name = n.get("displayName") or n.get("name") or ""
            if name:
                accounts.append((n["id"], name.strip(), n["readableId"]))
        page_info = data["data"]["accounts"]["pageInfo"]
        if not page_info["hasNextPage"]:
            break
        cursor = page_info["endCursor"]
    return accounts


def generate_username_variants(display_name):
    """Generate candidate PPPoE-style usernames from a Gaiia display name.

    "Jose Ortiz" -> {joseortiz, jortiz, jose, ortiz}
    "Graham McIntire" -> {grahammcintire, gmcintire, graham, mcintire}
    "Kory Biggs Shop" -> {korybiggsshop, kbiggs, kory, biggs, ...}
    "Mid Valley Pumping LLC" -> {midvalleypumpingllc, midvalley, ...}
    """
    name = display_name.lower().strip()
    # Remove punctuation
    name = re.sub(r'[^\w\s]', '', name)
    name = re.sub(r'\s+', ' ', name).strip()
    parts = name.split()

    variants = set()

    # Full concatenation: firstlast with no spaces
    variants.add("".join(parts))

    # First initial + last name: e.g. jortiz, gmcintire
    if len(parts) >= 2:
        variants.add(parts[0][0] + parts[-1])
        # Also first name + last name initial
        variants.add(parts[0] + parts[-1][0])

    # Each individual part
    for p in parts:
        variants.add(p)

    # First + last (just the two parts concatenated)
    if len(parts) >= 2:
        variants.add(parts[0] + parts[-1])

    # Handle numbered suffixes (e.g. "Perla Chavez 2" -> "perlachavez2")
    if len(parts) >= 3 and re.match(r'^\d+$', parts[-1]):
        variants.add("".join(parts[:-1]) + parts[-1])

    return variants


def parse_radius_input(text):
    """Parse the pipe-delimited RADIUS user table."""
    users = []
    pattern = r'\|\s*(\d+)\s*\|\s*(\S+)\s*\|\s*Clear(?:t|T)ext-Password\s*\|\s*:=\s*\|\s*(\S+)\s*\|'
    for match in re.finditer(pattern, text):
        users.append({
            "radius_id": int(match.group(1)),
            "username": match.group(2),
            "password": match.group(3),
        })
    return users


def match_users(radius_users, accounts):
    """Match RADIUS users to Gaiia accounts by exact username variant matching."""
    # Build mapping: username_variant -> list of (account_id, account_name, readable_id)
    variant_map = defaultdict(list)
    for acct_id, acct_name, readable_id in accounts:
        for variant in generate_username_variants(acct_name):
            variant_map[variant].append((acct_id, acct_name, readable_id))

    # Track which Gaiia accounts have been used (to avoid double-matching)
    used_accounts = set()

    results = []
    for user in radius_users:
        username = user["username"].lower()
        candidates = variant_map.get(username, [])

        # Filter out already-used accounts
        available = [(a, n, r) for a, n, r in candidates if a not in used_accounts]

        if len(available) == 1:
            acct_id, acct_name, readable_id = available[0]
            used_accounts.add(acct_id)
            results.append({
                **user,
                "gaiia_id": acct_id,
                "gaiia_name": acct_name,
                "gaiia_readable_id": readable_id,
                "match_type": "exact",
            })
        elif len(available) > 1:
            # Multiple candidates — leave blank for manual review
            results.append({
                **user,
                "gaiia_id": "",
                "gaiia_name": f"AMBIGUOUS: {', '.join(n for _,n,_ in available)}",
                "gaiia_readable_id": "",
                "match_type": "ambiguous",
            })
        else:
            results.append({
                **user,
                "gaiia_id": "",
                "gaiia_name": "",
                "gaiia_readable_id": "",
                "match_type": "no_match",
            })

    return results


def main():
    text = sys.stdin.read()

    radius_users = parse_radius_input(text)
    print(f"Parsed {len(radius_users)} RADIUS users", file=sys.stderr)

    print("Fetching Gaiia accounts...", file=sys.stderr)
    accounts = fetch_all_accounts()
    print(f"Fetched {len(accounts)} Gaiia accounts with names", file=sys.stderr)

    results = match_users(radius_users, accounts)

    writer = csv.writer(sys.stdout)
    writer.writerow(["radius_id", "username", "password", "gaiia_id", "gaiia_name", "gaiia_readable_id", "match_type"])
    for r in results:
        writer.writerow([
            r["radius_id"], r["username"], r["password"],
            r["gaiia_id"], r["gaiia_name"], r["gaiia_readable_id"], r["match_type"],
        ])

    matched = sum(1 for r in results if r["gaiia_id"])
    ambiguous = sum(1 for r in results if r["match_type"] == "ambiguous")
    unmatched = sum(1 for r in results if r["match_type"] == "no_match")
    print(f"\nMatched: {matched}, Ambiguous: {ambiguous}, No match: {unmatched} (total: {len(results)})", file=sys.stderr)

    if ambiguous:
        print("\nAMBIGUOUS (multiple Gaiia accounts match same username):", file=sys.stderr)
        for r in results:
            if r["match_type"] == "ambiguous":
                print(f"  {r['username']}: {r['gaiia_name']}", file=sys.stderr)

    if unmatched:
        print("\nNO MATCH:", file=sys.stderr)
        for r in results:
            if r["match_type"] == "no_match":
                print(f"  {r['username']}", file=sys.stderr)


if __name__ == "__main__":
    main()
