"""
scrylo/email_tools/email_guesser.py
-------------------------------------
Generates candidate email addresses from a name + domain.

Pattern coverage (~95% of real corporate email formats):
  first.last@     → most common  (~40%)
  first@          → small/startup (~20%)
  flast@          → larger orgs  (~12%)
  f.last@         → variant      (~7%)
  first_last@     → underscore   (~5%)
  firstl@         → first+last_initial (~4%)
  last@           → occasionally (~4%)
  last.first@     → European companies, esp. DACH (~3%)

FIXES vs previous version:
  - Removed self-overwrite bug on candidates[3] (firstl was being
    re-assigned after list construction, but only in the non-obvious
    index — now all patterns are explicit from the start).
  - Added last.first@ pattern (common in Germany/France/NL).
  - Added first-last@ hyphen variant (seen in .io startups).
  - _split_name now strips honorifics so they never appear in guesses
    (previously "Dr. Segun Aina" → "dr.segun.aina@..." was possible).
  - domain_from_url handles port numbers (e.g. company.com:8080).
"""

from __future__ import annotations

import re
import unicodedata


# ── Honorific prefixes — never include these in an email address ──────────

_HONORIFICS = {
    "dr", "mr", "mrs", "ms", "miss", "prof", "rev",
    "sir", "eng", "hon", "sr", "jr",
}


# ── Name normalisation ─────────────────────────────────────────────────────

def _normalise(name: str) -> str:
    """
    Convert accented/unicode name to ASCII-safe lowercase.
    "José García" → "jose garcia"
    """
    nfd = unicodedata.normalize("NFD", name)
    ascii_str = nfd.encode("ascii", "ignore").decode("ascii")
    return ascii_str.lower().strip()


def _split_name(full_name: str) -> tuple[str, str]:
    """
    Split full name into (first, last).
    - Strips honorific prefixes (Dr., Prof., Mr., etc.) so they never
      appear in generated email addresses.
    - Ignores middle names.
    - Returns ("", "") if name is unusable.

    Examples:
      "Dr. Segun Aina"    → ("segun", "aina")
      "Mary Jane Watson"  → ("mary", "watson")
      "Elon"              → ("elon", "")
      "Prof. Ana Ruiz"    → ("ana", "ruiz")
    """
    parts = _normalise(full_name).split()
    # Strip non-alpha chars per token, remove empty results
    parts = [re.sub(r"[^a-z]", "", p) for p in parts]
    parts = [p for p in parts if p]

    if not parts:
        return "", ""

    # Strip leading honorific tokens
    while parts and parts[0] in _HONORIFICS:
        parts = parts[1:]

    if not parts:
        return "", ""

    first = parts[0]
    last  = parts[-1] if len(parts) > 1 else ""
    return first, last


# ── Format generators ──────────────────────────────────────────────────────

def guess_emails(full_name: str, domain: str) -> list[str]:
    """
    Generate candidate email addresses for a person at a domain.
    Ordered from most to least likely based on observed corpus frequencies.

    Args:
        full_name: Person's full name e.g. "Jane Smith"
        domain:    Company domain e.g. "stackly.com" (no https://)

    Returns:
        Ordered list of candidate emails, most likely first.
        Returns [] if name or domain is unusable.
    """
    if not full_name or not domain:
        return []

    # Clean domain — strip protocol, path and port
    domain = re.sub(r"^https?://", "", domain)
    domain = domain.split("/")[0].split(":")[0].strip().lower()
    if not domain or "." not in domain:
        return []

    first, last = _split_name(full_name)
    if not first:
        return []

    candidates: list[str] = []

    if first and last:
        fi = first[0]   # first initial
        li = last[0]    # last initial

        candidates = [
            f"{first}.{last}@{domain}",     # jane.smith@      ~40%
            f"{first}@{domain}",            # jane@            ~20%
            f"{fi}{last}@{domain}",         # jsmith@          ~12%
            f"{fi}.{last}@{domain}",        # j.smith@          ~7%
            f"{first}_{last}@{domain}",     # jane_smith@       ~5%
            f"{first}{li}@{domain}",        # janes@            ~4%
            f"{last}@{domain}",             # smith@            ~4%
            f"{last}.{first}@{domain}",     # smith.jane@       ~3% (DACH/EU)
            f"{first}-{last}@{domain}",     # jane-smith@       ~2% (.io startups)
            f"{last}{fi}@{domain}",         # smithj@           ~1%
        ]

    elif first:
        # Only first name known — single best guess
        candidates = [f"{first}@{domain}"]

    # Deduplicate while preserving order
    seen: set[str] = set()
    unique = []
    for c in candidates:
        if c not in seen:
            seen.add(c)
            unique.append(c)

    return unique


def domain_from_url(url: str) -> str:
    """
    Extract bare domain from a URL or website string.
    Handles protocols, www prefix, paths, query strings and port numbers.

    "https://www.stackly.com/about"  → "stackly.com"
    "www.stackly.com"                → "stackly.com"
    "company.com:8080/team"          → "company.com"
    """
    if not url:
        return ""
    url = re.sub(r"^https?://", "", url)
    url = re.sub(r"^www\.", "", url)
    url = url.split("/")[0]   # strip path
    url = url.split(":")[0]   # strip port
    url = url.split("?")[0]   # strip query
    return url.strip().lower()


def guess_emails_from_lead(name: str, website: str) -> list[str]:
    """
    Convenience wrapper — takes a website URL and extracts domain automatically.
    """
    domain = domain_from_url(website)
    return guess_emails(name, domain)
