"""Contact email discovery for websites.

Strategies (in order of priority):
1. Extract emails directly from the crawled page (mailto links + regex)
2. Discover and scrape the site's contact/about pages
3. Use Hunter.io API for domain-level email lookup (optional, needs API key)
"""

import asyncio
import re
import os
from typing import List, Optional
from urllib.parse import urljoin, urlparse

import aiohttp
from bs4 import BeautifulSoup

EMAIL_PATTERN = re.compile(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}")

# Common contact page paths to check
CONTACT_PATHS = [
    "/contact",
    "/contact-us",
    "/about/contact",
    "/about",
    "/about-us",
    "/connect",
    "/get-in-touch",
    "/support",
    "/help",
]

SKIP_EMAIL_ENDINGS = (".png", ".jpg", ".gif", ".svg", ".jpeg", ".webp")
SKIP_EMAIL_DOMAINS = ("example.com", "yoursite", "domain.com", "sentry.io", "gravatar.com")
FREE_PROVIDERS = ("gmail.com", "yahoo.com", "hotmail.com", "outlook.com", "aol.com")

USER_AGENT = "Mozilla/5.0 (compatible; BrokenLinkChecker/1.0)"
REQUEST_TIMEOUT = 20


def extract_emails_from_html(html: str) -> set:
    """Extract all email addresses from HTML content."""
    soup = BeautifulSoup(html, "html.parser")
    emails = set()

    # From mailto: links
    for anchor in soup.find_all("a", href=True):
        href = anchor["href"]
        if href.startswith("mailto:"):
            email = href.replace("mailto:", "").split("?")[0].strip().lower()
            if "@" in email:
                emails.add(email)

    # From page text via regex
    page_text = soup.get_text()
    for email in EMAIL_PATTERN.findall(page_text):
        emails.add(email.lower())

    # Filter junk
    filtered = set()
    for email in emails:
        if any(email.endswith(ext) for ext in SKIP_EMAIL_ENDINGS):
            continue
        if any(skip in email for skip in SKIP_EMAIL_DOMAINS):
            continue
        filtered.add(email)

    return filtered


def score_email(email: str, target_domain: str = "") -> int:
    """Score an email for outreach relevance. Higher = better."""
    score = 0
    e = email.lower()

    # Career/outreach-relevant prefixes
    high_value = ["career", "employment", "job", "recruit", "hire", "talent",
                  "webmaster", "editor", "content", "marketing", "pr@", "press"]
    for kw in high_value:
        if kw in e:
            score += 100

    # Same domain as the target site
    if target_domain and target_domain in e:
        score += 50

    # Named person emails (likely decision makers)
    local = e.split("@")[0]
    if "." in local and not any(g in local for g in ["info", "contact", "support"]):
        score += 30

    # Generic prefixes are okay but not ideal
    generic = ["info@", "contact@", "hello@", "support@", "help@", "admin@"]
    for prefix in generic:
        if e.startswith(prefix):
            score -= 10

    # Free email providers are low value for outreach
    for provider in FREE_PROVIDERS:
        if provider in e:
            score -= 80

    return score


class ContactFinder:
    """Find contact emails for a website using multiple strategies."""

    def __init__(self, hunter_api_key: Optional[str] = None):
        self.hunter_api_key = hunter_api_key or os.environ.get("HUNTER_API_KEY")

    async def find_emails(
        self, url: str, page_html: Optional[str] = None
    ) -> List[str]:
        """Find contact emails for a URL using all available strategies.

        Args:
            url: The website URL to find contacts for.
            page_html: If you already have the page HTML, pass it to skip a fetch.

        Returns:
            List of emails sorted by outreach relevance (best first).
        """
        parsed = urlparse(url)
        domain = parsed.netloc
        base_url = f"{parsed.scheme}://{domain}"
        all_emails = set()

        timeout = aiohttp.ClientTimeout(total=REQUEST_TIMEOUT)
        async with aiohttp.ClientSession(
            timeout=timeout, headers={"User-Agent": USER_AGENT}
        ) as session:
            # Strategy 1: Extract from the provided page
            if page_html:
                all_emails.update(extract_emails_from_html(page_html))

            # Strategy 2: Check contact/about pages
            contact_emails = await self._scrape_contact_pages(session, base_url)
            all_emails.update(contact_emails)

            # Strategy 3: Hunter.io domain search
            if self.hunter_api_key:
                hunter_emails = await self._hunter_lookup(session, domain)
                all_emails.update(hunter_emails)

        # Score and sort
        sorted_emails = sorted(
            all_emails, key=lambda e: score_email(e, domain), reverse=True
        )
        return sorted_emails

    async def _scrape_contact_pages(
        self, session: aiohttp.ClientSession, base_url: str
    ) -> set:
        """Try common contact page URLs and extract emails."""
        emails = set()

        async def try_path(path: str):
            url = urljoin(base_url, path)
            try:
                async with session.get(url, allow_redirects=True) as resp:
                    if resp.status == 200:
                        html = await resp.text()
                        return extract_emails_from_html(html)
            except Exception:
                pass
            return set()

        # Check paths concurrently (but limit concurrency to be polite)
        semaphore = asyncio.Semaphore(3)

        async def limited_try(path):
            async with semaphore:
                return await try_path(path)

        results = await asyncio.gather(
            *[limited_try(p) for p in CONTACT_PATHS], return_exceptions=True
        )

        for result in results:
            if isinstance(result, set):
                emails.update(result)

        return emails

    async def _hunter_lookup(
        self, session: aiohttp.ClientSession, domain: str
    ) -> set:
        """Look up emails via Hunter.io domain search API."""
        emails = set()
        url = "https://api.hunter.io/v2/domain-search"
        params = {"domain": domain, "api_key": self.hunter_api_key}

        try:
            async with session.get(url, params=params) as resp:
                if resp.status == 200:
                    data = await resp.json()
                    for entry in data.get("data", {}).get("emails", []):
                        value = entry.get("value", "").lower()
                        if value:
                            emails.add(value)
        except Exception:
            pass

        return emails
