"""Scoring capability - calculates quality and priority scores for leads.

Ported from gardener-fork/lib/scoring.py with dict-in/dict-out interface.
"""

import re
from datetime import datetime
from pathlib import Path
import json
from typing import Dict, Tuple, Optional

from capabilities.patterns import is_shell_lead


CONFIG_PATH = Path("./config/gardener.json")
SHELL_PATTERNS_PATH = Path("./config/enhanced_shell_patterns.json")


def _load_config() -> Dict:
    """Load gardener.json config."""
    if not CONFIG_PATH.exists():
        return {}
    try:
        with open(CONFIG_PATH, 'r') as f:
            return json.load(f)
    except (json.JSONDecodeError, IOError):
        return {}


def _load_shell_patterns() -> Dict:
    """Load shell patterns config."""
    if not SHELL_PATTERNS_PATH.exists():
        return {"name_penalty_patterns": [], "name_bonus_patterns": []}
    try:
        with open(SHELL_PATTERNS_PATH, 'r') as f:
            return json.load(f)
    except (json.JSONDecodeError, IOError):
        return {"name_penalty_patterns": [], "name_bonus_patterns": []}


def _score_naics(naics_code: str, naics_desc: str, tiers: Dict) -> Tuple[int, str]:
    """Score business based on NAICS code using tier config.
    
    Returns (score, tier_name).
    """
    code = ""
    if naics_code:
        code = re.sub(r'[^0-9]', '', str(naics_code).split('(')[-1].rstrip(')'))
        if not code:
            code = re.sub(r'[^0-9]', '', str(naics_code))
    
    for tier_name in ["tier_1", "tier_2", "tier_3", "tier_4", "tier_0"]:
        tier_data = tiers.get("tiers", {}).get(tier_name, {})
        codes = tier_data.get("codes", {})
        if code and code in codes:
            return codes[code]["score"], tier_name
    
    desc_lower = (naics_desc or naics_code or "").lower()
    kb = tiers.get("keyword_fallback", {})
    
    for tier_key in ["tier_0_keywords", "tier_1_keywords", "tier_2_keywords", "tier_3_keywords", "tier_4_keywords"]:
        tier_kb = kb.get(tier_key, {})
        keywords = tier_kb.get("match_any", [])
        for kw in keywords:
            if kw.lower() in desc_lower:
                return tier_kb["score"], tier_key.replace("_keywords", "")
    
    default_score = kb.get("no_match_default", {}).get("score", 5)
    return default_score, "default"


def _score_name(name: str, shell_patterns: Dict, naics_code: str = "") -> int:
    """Score business name using shell patterns.
    
    Applies penalties first (floor -50), then bonuses (ceiling +25).
    """
    total_penalty = 0
    total_bonus = 0
    matched_bonuses = []
    
    for p in shell_patterns.get("name_penalty_patterns", []):
        pattern = p["pattern"]
        penalty = p["penalty"]
        flags_str = p.get("flags", "")
        pid = p.get("id", "")
        
        if pid == "development_without_construction_naics":
            naics_str = str(naics_code or "")
            skip = False
            for prefix in ["236", "237", "531"]:
                if naics_str.startswith(prefix) or prefix in naics_str.lower():
                    skip = True
                    break
            if skip:
                continue
        
        try:
            flags = 0
            if "i" in flags_str.lower():
                flags |= re.IGNORECASE
            if re.search(pattern, name, flags):
                if pid == "group_without_profession":
                    total_penalty += penalty
                    matched_bonuses.append(("_group_penalty", penalty))
                else:
                    total_penalty += penalty
        except re.error:
            pass
    
    penalty_floor = shell_patterns.get("scoring_rules", {}).get("penalty_floor", -50)
    total_penalty = max(total_penalty, penalty_floor)
    
    for b in shell_patterns.get("name_bonus_patterns", []):
        pattern = b["pattern"]
        bonus = b["bonus"]
        flags_str = b.get("flags", "")
        bid = b.get("id", "")
        
        try:
            flags = 0
            if "i" in flags_str.lower():
                flags |= re.IGNORECASE
            if re.search(pattern, name, flags):
                total_bonus += bonus
                matched_bonuses.append((bid, bonus))
        except re.error:
            pass
    
    bonus_ids = [bid for bid, _ in matched_bonuses]
    if "_group_penalty" in bonus_ids and len([bid for bid in bonus_ids if bid != "_group_penalty"]) > 0:
        group_penalty_val = next(p for pid, p in matched_bonuses if pid == "_group_penalty")
        total_penalty -= group_penalty_val
    
    bonus_ceiling = shell_patterns.get("scoring_rules", {}).get("bonus_ceiling", 25)
    total_bonus = min(total_bonus, bonus_ceiling)
    
    return total_penalty + total_bonus


def classify_email_domain(email: str, cfg: Optional[Dict] = None) -> Tuple[str, Optional[str]]:
    """Classify email domain tier from config. Single source of truth.
    
    Returns (tier_name, domain) where tier_name is one of:
    - "professional_tld"
    - "custom_business_domain"
    - "free_provider"
    - "no_email"
    """
    if cfg is None:
        cfg = _load_config()
    
    if not email or "@" not in email:
        return "no_email", None
    
    domain = email.split("@")[-1].lower().strip()
    
    bonus_cfg = cfg.get("contact_info_bonus", {})
    professional_tlds = bonus_cfg.get("email_domain_tiers", {}).get("professional_tld", {}).get("match_any", [])
    
    for tld in professional_tlds:
        if domain.endswith(tld.lower()):
            return "professional_tld", domain
    
    free_domains = bonus_cfg.get("free_provider_domains", [])
    if domain in free_domains:
        return "free_provider", domain
    
    return "custom_business_domain", domain


def _score_email_domain(email: str, cfg: Dict) -> Tuple[int, str, Optional[str]]:
    """Classify email into domain tier and return bonus.
    
    Returns (bonus, tier_name, domain).
    """
    if not email or "@" not in email:
        return 0, "no_email", None
    
    domain = email.split("@")[-1].lower().strip()
    
    bonus_cfg = cfg.get("contact_info_bonus", {})
    professional_tlds = bonus_cfg.get("email_domain_tiers", {}).get("professional_tld", {}).get("match_any", [])
    
    for tld in professional_tlds:
        if domain.endswith(tld.lower()):
            return 15, "professional_tld", domain
    
    free_domains = bonus_cfg.get("free_provider_domains", [])
    if domain in free_domains:
        return 5, "free_provider", domain
    
    return 12, "custom_business_domain", domain


def _recency_bonus(filing_date: str, cfg: Dict) -> int:
    """Calculate recency score from filing date."""
    if not filing_date:
        return 0
    
    try:
        filed = datetime.strptime(filing_date[:10], "%Y-%m-%d")
        days_old = (datetime.utcnow() - filed).days
        
        rb = cfg.get("recency_bonus", {})
        if days_old <= 2:
            return rb.get("0_2_days", 20)
        elif days_old <= 5:
            return rb.get("3_5_days", 15)
        elif days_old <= 10:
            return rb.get("6_10_days", 10)
        elif days_old <= 20:
            return rb.get("11_20_days", 5)
        return 0
    except Exception:
        return 0


def _get_location_bonus(city: str, cfg: Dict) -> int:
    """Get location bonus for CT city."""
    loc = cfg.get("location_quality", {})
    cities = cfg.get("known_cities", {})
    
    if isinstance(cities, dict) and "cities" in cities:
        cities = {c.lower(): 5 for c in cities["cities"]}
    
    city_lower = (city or "").lower().strip()
    
    if not city_lower or city_lower == "ct":
        return 1
    
    if city_lower in cities:
        return loc.get("known_ct_city", 5)
    
    return loc.get("generic_ct_or_missing", 3)


def _is_pllc_fast_track(name: str, business_type: str, naics_raw: str, cfg: Dict) -> Optional[Tuple[str, int]]:
    """Check if lead qualifies for PLLC fast-track scoring.
    
    Returns (tier_level, base_score) or None.
    """
    ft = cfg.get("pllc_fast_track", {})
    if not ft.get("enabled", False):
        return None
    
    name_upper = name.upper()
    
    if "PLLC" in name_upper or "P.L.L.C" in name_upper:
        return ("primary", ft.get("detection", {}).get("primary", {}).get("base_score", 92))
    
    detection = ft.get("detection", {}).get("secondary", {})
    credential_suffixes = detection.get("credential_suffixes", [])
    professional_keywords = detection.get("professional_keywords", [])
    
    for suffix in credential_suffixes:
        # Guard against false positives like "MD Development Group"
        # Credential must appear:
        # 1. After a comma (e.g., "John Smith, MD")
        # 2. After a period (e.g., "John Smith M.D.")
        # 3. At the end of the name, adjacent to entity type (e.g., "Smith MD PLLC")
        # NOT as a standalone word in the middle of a business name
        escaped = re.escape(suffix)
        patterns = [
            rf',\s*{escaped}\b',           # After comma
            rf'\.\s*{escaped}\b',           # After period
            rf'\b{escaped}\s+(?:LLC|PLLC|INC|Corp|LTD|P\.C|L\.L\.C|P\.L\.C)\b',  # Before entity type
            rf'\b{escaped}$',               # At end of name
        ]
        for pat in patterns:
            if re.search(pat, name, re.I):
                return ("secondary", detection.get("base_score", 82))
    
    name_lower = name.lower()
    for kw in professional_keywords:
        if kw.lower() in name_lower:
            return ("secondary", detection.get("base_score", 82))
    
    return None


def score_lead(lead: Dict, config: Optional[Dict] = None) -> Dict:
    """Calculate quality score for a lead and update the lead dict.
    
    Args:
        lead: Lead dict with name, city, naics, filing_date, email, phone, etc.
        config: Optional config dict (loads from file if not provided)
    
    Returns:
        Updated lead dict with added fields:
        - score: int (0-100)
        - naics_score: int
        - name_score: int
        - tier: str
        - is_shell: bool
    """
    if config is None:
        config = _load_config()
    
    shell_patterns = _load_shell_patterns()
    
    name = lead.get("name", "")
    city = lead.get("city", "")
    naics_raw = lead.get("naics", "")
    filing_date = lead.get("filing_date", "")
    email = lead.get("email", "")
    phone = lead.get("phone", "")
    business_type = lead.get("entity_type", "")
    citizenship = lead.get("citizenship", "")
    
    ft_result = _is_pllc_fast_track(name, business_type, naics_raw, config)
    
    if ft_result:
        ft_level, base = ft_result
        ft = config.get("pllc_fast_track", {})
        mods = ft.get("modifiers", {})
        score = base
        
        if filing_date:
            try:
                filed = datetime.strptime(filing_date[:10], "%Y-%m-%d")
                days_old = (datetime.utcnow() - filed).days
                rec = mods.get("recency", {})
                if days_old <= 2:
                    score += rec.get("0_2_days", 5)
                elif days_old <= 5:
                    score += rec.get("3_5_days", 3)
                elif days_old <= 10:
                    score += rec.get("6_10_days", 1)
            except Exception:
                pass
        
        loc = mods.get("location", {})
        city_lower = (city or "").lower().strip()
        cities = config.get("known_cities", {})
        if isinstance(cities, dict) and "cities" in cities:
            cities = {c.lower(): 5 for c in cities["cities"]}
        
        if not city_lower or city_lower == "ct":
            score += 0
        elif city_lower in cities:
            score += loc.get("known_ct_city", 3)
        else:
            score += loc.get("generic_ct", 1)
        
        state = (citizenship or "").strip().lower()
        if state == "foreign" or (state and state != "domestic" and state != "connecticut"):
            score += loc.get("out_of_state", -92)
        
        score = max(0, min(ft.get("max_score", 100), score))
        
        lead["score"] = score
        lead["naics_score"] = 0
        lead["name_score"] = 0
        lead["tier"] = "pllc_fast_track"
        lead["is_shell"] = False
        
        return lead
    
    score = 0
    
    naics_score, tier_name = _score_naics(None, naics_raw, config)
    score += naics_score
    lead["naics_score"] = naics_score
    lead["tier"] = tier_name
    
    score += _recency_bonus(filing_date, config)
    
    name_score = _score_name(name, shell_patterns, naics_code=naics_raw)
    score += name_score
    lead["name_score"] = name_score
    
    is_shell = name_score <= -25
    lead["is_shell"] = is_shell
    
    email_bonus, email_tier, _ = _score_email_domain(email, config)
    score += email_bonus
    
    if phone:
        score += 5
    
    score += _get_location_bonus(city, config)
    
    fs = config.get("formation_signals", {})
    
    pllc_in_name = any(
        re.search(b["pattern"], name, re.I)
        for b in shell_patterns.get("name_bonus_patterns", [])
        if b.get("id") == "pllc_entity"
    )
    if not pllc_in_name:
        if business_type and "pllc" in business_type.lower():
            score += fs.get("pllc_bonus", 12)
    
    if citizenship == "Foreign":
        penalty = fs.get("foreign_penalty", -10)
        if tier_name == "tier_1":
            penalty = -3
        score += penalty
    elif citizenship == "Domestic":
        score += fs.get("domestic_bonus", 5)
    
    if business_type and "stock" in business_type.lower():
        if tier_name not in ("tier_1", "tier_2"):
            score += fs.get("stock_corp_penalty", -8)
    
    if is_shell:
        score = score // 2
    
    score = max(0, min(100, score))
    lead["score"] = score
    
    return lead


def calculate_priority(lead: Dict, config: Optional[Dict] = None) -> Dict:
    """Calculate composite priority from score × readiness_weight.
    
    Args:
        lead: Lead dict with enrichment fields populated
        config: Optional config dict
    
    Returns:
        Dict with:
        - priority: float (sort key for call lists)
        - readiness_weight: float (0.05-1.0)
        - readiness_signals: list of strings
    """
    if config is None:
        config = _load_config()
    
    weights = {
        "phone": 0.50,
        "custom_email": 0.15,
        "professional_tld": 0.15,
        "website": 0.10,
        "displacement": 0.10,
        "milestone": 0.10,
        "domestic": 0.03,
        "recent_filing": 0.02,
    }
    
    weight = 0.05
    signals = []
    
    has_phone = bool(
        lead.get("phone") or lead.get("domain_phone") or lead.get("brave_phone")
    )
    if has_phone:
        weight += weights["phone"]
        signals.append(f"phone +{weights['phone']:.2f}")
    
    email = lead.get("email", "")
    _, email_tier, _ = _score_email_domain(email, config)
    
    if email_tier == "professional_tld":
        weight += weights["professional_tld"]
        signals.append(f"professional_tld +{weights['professional_tld']:.2f}")
    elif email_tier == "custom_business_domain":
        weight += weights["custom_email"]
        signals.append(f"custom_email +{weights['custom_email']:.2f}")
    
    if lead.get("website_exists"):
        weight += weights["website"]
        signals.append(f"website +{weights['website']:.2f}")
    
    if lead.get("competitor_displacement"):
        weight += weights["displacement"]
        signals.append(f"displacement +{weights['displacement']:.2f}")
    
    if lead.get("source") == "historical" and lead.get("milestone_window_hit"):
        weight += weights["milestone"]
        label = lead.get("milestone_year", "?")
        signals.append(f"milestone({label}yr) +{weights['milestone']:.2f}")
    
    if lead.get("citizenship", "") == "Domestic":
        weight += weights["domestic"]
        signals.append(f"domestic +{weights['domestic']:.2f}")
    
    filing_date = lead.get("filing_date", "")
    if filing_date:
        try:
            filed = datetime.strptime(filing_date[:10], "%Y-%m-%d")
            days_old = (datetime.utcnow() - filed).days
            if days_old <= 30:
                weight += weights["recent_filing"]
                signals.append(f"recent({days_old}d) +{weights['recent_filing']:.2f}")
        except Exception:
            pass
    
    # Filing activity signals (new)
    if lead.get("has_recent_amendment"):
        weight += 0.08
        signals.append("recent_amendment +0.08")
    
    if lead.get("uses_paper_filing"):
        weight += 0.05
        signals.append("paper_filer +0.05")
    
    if lead.get("annual_report_current"):
        weight += 0.03
        signals.append("annual_report_current +0.03")
    
    weight = min(weight, 1.0)
    quality_score = lead.get("score", 0)
    priority = round(quality_score * weight, 1)
    
    lead["priority"] = priority
    lead["readiness_weight"] = round(weight, 2)
    lead["readiness_signals"] = signals
    
    return {
        "priority": priority,
        "readiness_weight": round(weight, 2),
        "readiness_signals": signals,
    }