# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Nonprofit Management Skill

Track donors, grants, and nonprofit-specific operations.
Data stored in ~/.familiar/data/nonprofit.json
"""

import json
from datetime import datetime, timedelta
from pathlib import Path

# AFP-standard donor field defaults (added in P3 upgrade)
_DONOR_DEFAULTS = {
    # RFM scoring
    "donor_level": "",
    "rfm_score": 0,
    "recency_score": 0,
    "frequency_score": 0,
    "monetary_score": 0,
    "lifetime_value": 0,
    # LYBUNT/SYBUNT
    "lybunt": False,
    "sybunt": False,
    "lapsed": False,
    "lapsed_date": "",
    # Giving pattern
    "avg_gift": 0,
    "largest_gift": 0,
    "gift_count": 0,
    "giving_frequency": "",
    "preferred_channel": "",
    # Cultivation
    "acquisition_source": "",
    "cultivation_stage": "",
    "solicitation_history": [],
    "stewardship_plan": "",
    # Communication preferences
    "communication_pref": "",
    "do_not_solicit": False,
    "do_not_email": False,
    "do_not_call": False,
    "do_not_mail": False,
    # Org / compliance
    "is_org": False,
    "org_type": "",
    "employer_match": False,
    "tax_id": "",
}

# Grant enhancement field defaults (added in P5 upgrade)
_GRANT_DEFAULTS = {
    "cfda_number": "",
    "funder_ein": "",
    "opportunity_number": "",
    "grant_type": "",
    "budget_total": 0,
    "budget_spent": 0,
    "indirect_rate": 0,
    "match_required": 0,
    "match_secured": 0,
    "reporting_schedule": "",
    "reports": [],
    "compliance_notes": "",
    "award_number": "",
    "program_officer": "",
    "program_officer_email": "",
    "program_officer_phone": "",
}


def _get_data_dir():
    """Get data directory (tenant-scoped in Reflection, default in standalone)."""
    try:
        from familiar.core import paths

        return paths.DATA_DIR
    except ImportError:
        return Path.home() / ".familiar" / "data"


import logging  # noqa: E402

logger = logging.getLogger(__name__)


def _get_data_file():
    """Get data file path (re-evaluates for tenant scoping)."""
    return _get_data_dir() / "nonprofit.json"


DATA_FILE = _get_data_file()  # Default for backward compat


def _load_data() -> dict:
    """Load nonprofit data."""
    if _get_data_file().exists():
        try:
            with open(_get_data_file(), encoding="utf-8") as f:
                data = json.load(f)
            # Ensure all collections exist (added over time)
            data.setdefault("donors", [])
            data.setdefault("grants", [])
            data.setdefault("notes", [])
            data.setdefault("compliance", [])
            data.setdefault("legislation", [])
            data.setdefault("bill_watchlist", [])
            return data
        except (json.JSONDecodeError, IOError, OSError) as e:
            logger.warning(f"Failed to load nonprofit data: {e}")
    return {
        "donors": [],
        "grants": [],
        "notes": [],
        "compliance": [],
        "legislation": [],
        "bill_watchlist": [],
    }


def _save_data(data: dict):
    """Save nonprofit data."""
    _get_data_file().parent.mkdir(parents=True, exist_ok=True)
    with open(_get_data_file(), "w") as f:
        json.dump(data, f, indent=2)


def _generate_id() -> str:
    import random
    import string

    return "".join(random.choices(string.ascii_lowercase + string.digits, k=6))


def _backfill_donor(donor: dict) -> dict:
    """Backfill AFP-standard fields onto legacy donor records."""
    for key, default in _DONOR_DEFAULTS.items():
        if key not in donor:
            donor[key] = default if not isinstance(default, list) else list(default)
    return donor


def _backfill_grant(grant: dict) -> dict:
    """Backfill enhanced fields onto legacy grant records."""
    for key, default in _GRANT_DEFAULTS.items():
        if key not in grant:
            grant[key] = default if not isinstance(default, list) else list(default)
    return grant


def _compute_rfm(donor: dict) -> dict:
    """Compute RFM scores and donor level (AFP standard).

    Recency: days since last gift → 5 (<30d), 4 (<90d), 3 (<180d), 2 (<365d), 1 (>365d)
    Frequency: gifts per year → 5 (12+), 4 (4-11), 3 (2-3), 2 (1), 1 (0)
    Monetary: avg gift → 5 ($10k+), 4 ($1k+), 3 ($250+), 2 ($50+), 1 (<$50)
    """
    now = datetime.now()
    gifts = [
        i for i in donor.get("interactions", []) if i.get("type") == "gift" and i.get("amount")
    ]

    if not gifts:
        donor["rfm_score"] = 0
        donor["recency_score"] = 0
        donor["frequency_score"] = 0
        donor["monetary_score"] = 0
        donor["donor_level"] = "prospect"
        donor["avg_gift"] = 0
        donor["largest_gift"] = 0
        donor["gift_count"] = 0
        return donor

    # Gift amounts and dates
    amounts = [float(g["amount"]) for g in gifts]
    dates = []
    for g in gifts:
        try:
            dates.append(datetime.fromisoformat(g["date"]))
        except (ValueError, TypeError, KeyError):
            pass

    donor["gift_count"] = len(gifts)
    donor["avg_gift"] = sum(amounts) / len(amounts) if amounts else 0
    donor["largest_gift"] = max(amounts) if amounts else 0

    # Recency score
    if dates:
        days_since = (now - max(dates)).days
        if days_since < 30:
            donor["recency_score"] = 5
        elif days_since < 90:
            donor["recency_score"] = 4
        elif days_since < 180:
            donor["recency_score"] = 3
        elif days_since < 365:
            donor["recency_score"] = 2
        else:
            donor["recency_score"] = 1
    else:
        donor["recency_score"] = 1

    # Frequency score — gifts per year
    if dates and len(dates) >= 2:
        span_days = max((max(dates) - min(dates)).days, 1)
        gifts_per_year = len(gifts) / (span_days / 365.25)
    elif dates:
        gifts_per_year = 1
    else:
        gifts_per_year = 0

    if gifts_per_year >= 12:
        donor["frequency_score"] = 5
    elif gifts_per_year >= 4:
        donor["frequency_score"] = 4
    elif gifts_per_year >= 2:
        donor["frequency_score"] = 3
    elif gifts_per_year >= 1:
        donor["frequency_score"] = 2
    else:
        donor["frequency_score"] = 1

    # Monetary score
    avg = donor["avg_gift"]
    if avg >= 10000:
        donor["monetary_score"] = 5
    elif avg >= 1000:
        donor["monetary_score"] = 4
    elif avg >= 250:
        donor["monetary_score"] = 3
    elif avg >= 50:
        donor["monetary_score"] = 2
    else:
        donor["monetary_score"] = 1

    # Composite RFM (3-15)
    donor["rfm_score"] = donor["recency_score"] + donor["frequency_score"] + donor["monetary_score"]

    # Donor level based on total giving and RFM
    total = donor.get("total_given", 0)
    if total >= 10000:
        donor["donor_level"] = "major"
    elif total >= 1000:
        donor["donor_level"] = "mid"
    elif total > 0:
        donor["donor_level"] = "small"
    else:
        donor["donor_level"] = "prospect"

    # Check if lapsed (no gift in 18+ months)
    if dates:
        days_since_last = (now - max(dates)).days
        if days_since_last > 540:  # 18 months
            donor["lapsed"] = True
            donor["lapsed_date"] = max(dates).isoformat()
            donor["donor_level"] = "lapsed"
        else:
            donor["lapsed"] = False
            donor["lapsed_date"] = ""

    # Lifetime value projection (simple: avg_gift * gifts_per_year * 5 years)
    donor["lifetime_value"] = round(donor["avg_gift"] * max(gifts_per_year, 1) * 5, 2)

    # Giving frequency classification
    if gifts_per_year >= 12:
        donor["giving_frequency"] = "monthly"
    elif gifts_per_year >= 4:
        donor["giving_frequency"] = "quarterly"
    elif gifts_per_year >= 2:
        donor["giving_frequency"] = "semi-annual"
    elif gifts_per_year >= 1:
        donor["giving_frequency"] = "annual"
    else:
        donor["giving_frequency"] = "one-time"

    return donor


def _compute_lybunt_sybunt(donor: dict) -> dict:
    """Compute LYBUNT/SYBUNT flags.

    LYBUNT: Last Year But Unfortunately Not This — gave last calendar year, not yet this year
    SYBUNT: Some Year But Unfortunately Not This — gave some prior year, not recent 2 years
    """
    now = datetime.now()
    current_year = now.year
    last_year = current_year - 1

    gifts_by_year = {}
    for i in donor.get("interactions", []):
        if i.get("type") == "gift" and i.get("amount"):
            try:
                year = datetime.fromisoformat(i["date"]).year
                gifts_by_year.setdefault(year, []).append(i)
            except (ValueError, TypeError, KeyError):
                pass

    gave_this_year = current_year in gifts_by_year
    gave_last_year = last_year in gifts_by_year
    gave_any_prior = any(y < last_year for y in gifts_by_year)

    donor["lybunt"] = gave_last_year and not gave_this_year
    donor["sybunt"] = not gave_last_year and not gave_this_year and gave_any_prior

    return donor


# === Donor Management ===


def log_donor(data: dict) -> str:
    """Log a donor interaction or gift."""
    name = data.get("name", "").strip()
    if not name:
        return "Please provide donor name."

    db = _load_data()

    # Find or create donor
    donor = None
    for d in db["donors"]:
        if d["name"].lower() == name.lower():
            donor = d
            break

    if not donor:
        donor = {
            "id": _generate_id(),
            "name": name,
            "email": data.get("email", ""),
            "phone": data.get("phone", ""),
            "total_given": 0,
            "first_gift": None,
            "last_gift": None,
            "interactions": [],
            "notes": data.get("notes", ""),
            "tags": data.get("tags", []),
        }
        # AFP-standard fields
        for key, default in _DONOR_DEFAULTS.items():
            val = data.get(key)
            donor[key] = (
                val
                if val is not None
                else (default if not isinstance(default, list) else list(default))
            )
        db["donors"].append(donor)
    else:
        _backfill_donor(donor)

    # Log interaction/gift
    interaction = {
        "date": datetime.now().isoformat(),
        "type": data.get("type", "note"),  # gift, email, call, meeting, note
        "amount": data.get("amount"),
        "description": data.get("description", ""),
    }

    donor["interactions"].append(interaction)

    # Update stats if gift
    if interaction["type"] == "gift" and interaction["amount"]:
        amount = float(interaction["amount"])
        donor["total_given"] = donor.get("total_given", 0) + amount
        donor["last_gift"] = datetime.now().isoformat()
        if not donor.get("first_gift"):
            donor["first_gift"] = datetime.now().isoformat()

    # Update contact info if provided
    if data.get("email"):
        donor["email"] = data["email"]
    if data.get("phone"):
        donor["phone"] = data["phone"]
    if data.get("notes"):
        donor["notes"] = data["notes"]

    # Recompute AFP metrics after any gift interaction
    if interaction["type"] == "gift":
        _compute_rfm(donor)
        _compute_lybunt_sybunt(donor)

    _save_data(db)

    if interaction["type"] == "gift":
        level = donor.get("donor_level", "")
        rfm = donor.get("rfm_score", 0)
        return (
            f"✅ Logged ${interaction['amount']} gift from {name}. "
            f"Total: ${donor['total_given']:.2f} | Level: {level} | RFM: {rfm}"
        )
    else:
        return f"✅ Logged {interaction['type']} with {name}"


def search_donors(data: dict) -> str:
    """Search donor database."""
    db = _load_data()
    query = data.get("query", "").lower()
    min_amount = data.get("min_total")

    results = []
    for donor in db["donors"]:
        if (
            query
            and query not in donor["name"].lower()
            and query not in donor.get("email", "").lower()
        ):
            continue
        if min_amount and donor.get("total_given", 0) < min_amount:
            continue
        results.append(donor)

    if not results:
        return "No donors found matching criteria."

    # Sort by total given
    results.sort(key=lambda x: x.get("total_given", 0), reverse=True)

    lines = [f"👥 Donors ({len(results)}):\n"]
    for d in results[:20]:
        total = d.get("total_given", 0)
        last = ""
        if d.get("last_gift"):
            try:
                last_date = datetime.fromisoformat(d["last_gift"]).strftime("%b %Y")
                last = f" (last: {last_date})"
            except (ValueError, TypeError) as e:
                logger.debug("Value conversion error suppressed: %s", e)
        email = f" - {d['email']}" if d.get("email") else ""
        lines.append(f"  [{d['id']}] {d['name']}: ${total:.2f}{last}{email}")

    return "\n".join(lines)


def donor_details(data: dict) -> str:
    """Get detailed donor information."""
    donor_id = data.get("id", "").lower()
    name = data.get("name", "").lower()

    db = _load_data()

    donor = None
    for d in db["donors"]:
        if d["id"] == donor_id or d["name"].lower() == name:
            donor = d
            break

    if not donor:
        return "Donor not found."

    lines = [
        f"👤 {donor['name']}",
        f"   ID: {donor['id']}",
    ]

    if donor.get("email"):
        lines.append(f"   Email: {donor['email']}")
    if donor.get("phone"):
        lines.append(f"   Phone: {donor['phone']}")

    lines.append(f"   Total Given: ${donor.get('total_given', 0):.2f}")

    if donor.get("first_gift"):
        lines.append(f"   First Gift: {donor['first_gift'][:10]}")
    if donor.get("last_gift"):
        lines.append(f"   Last Gift: {donor['last_gift'][:10]}")

    if donor.get("notes"):
        lines.append(f"   Notes: {donor['notes']}")

    # Recent interactions
    interactions = donor.get("interactions", [])[-5:]
    if interactions:
        lines.append("\n   Recent Activity:")
        for i in reversed(interactions):
            date = i["date"][:10]
            if i["type"] == "gift":
                lines.append(f"     {date}: Gift ${i.get('amount', 0)}")
            else:
                desc = i.get("description", "")[:40]
                lines.append(f"     {date}: {i['type']} - {desc}")

    return "\n".join(lines)


def donors_needing_thanks(data: dict) -> str:
    """Find donors who need thank you letters (gift in last 30 days, no thanks logged)."""
    db = _load_data()
    days = data.get("days", 30)
    cutoff = datetime.now() - timedelta(days=days)

    needs_thanks = []

    for donor in db["donors"]:
        # Check for recent gifts
        recent_gifts = []
        has_thanks = False

        for interaction in donor.get("interactions", []):
            try:
                int_date = datetime.fromisoformat(interaction["date"])
                if int_date > cutoff:
                    if interaction["type"] == "gift":
                        recent_gifts.append(interaction)
                    elif interaction["type"] == "thanks":
                        has_thanks = True
            except (ValueError, TypeError, KeyError) as e:
                logger.debug("Value conversion error suppressed: %s", e)
        if recent_gifts and not has_thanks:
            total_recent = sum(float(g.get("amount", 0)) for g in recent_gifts)
            needs_thanks.append(
                {"donor": donor, "amount": total_recent, "count": len(recent_gifts)}
            )

    if not needs_thanks:
        return "✅ All recent donors have been thanked!"

    needs_thanks.sort(key=lambda x: x["amount"], reverse=True)

    lines = [f"📝 Donors Needing Thank You ({len(needs_thanks)}):\n"]
    for item in needs_thanks:
        d = item["donor"]
        lines.append(f"  🔴 {d['name']}: ${item['amount']:.2f} ({item['count']} gift(s))")
        if d.get("email"):
            lines.append(f"      {d['email']}")

    return "\n".join(lines)


# === Grant Management ===


def add_grant(data: dict) -> str:
    """Add a grant to track."""
    name = data.get("name", "").strip()
    funder = data.get("funder", "").strip()

    if not name:
        return "Please provide grant name."

    db = _load_data()

    grant = {
        "id": _generate_id(),
        "name": name,
        "funder": funder,
        "amount": data.get("amount"),
        "status": data.get("status", "prospect"),
        "deadline": data.get("deadline"),
        "report_due": data.get("report_due"),
        "start_date": data.get("start_date"),
        "end_date": data.get("end_date"),
        "notes": data.get("notes", ""),
        "created_at": datetime.now().isoformat(),
    }
    # Enhanced grant fields (P5 upgrade)
    for key, default in _GRANT_DEFAULTS.items():
        val = data.get(key)
        grant[key] = (
            val
            if val is not None
            else (default if not isinstance(default, list) else list(default))
        )

    db["grants"].append(grant)
    _save_data(db)

    return f"✅ Added grant: {name} from {funder}"


def list_grants(data: dict) -> str:
    """List grants with optional status filter."""
    db = _load_data()
    status_filter = data.get("status", "").lower()

    grants = db.get("grants", [])

    if status_filter:
        grants = [g for g in grants if g.get("status", "").lower() == status_filter]

    if not grants:
        return "No grants found."

    # Sort by deadline
    def sort_key(g):
        return g.get("deadline") or g.get("report_due") or "9999"

    grants.sort(key=sort_key)

    status_emoji = {
        "prospect": "🔵",
        "applied": "🟡",
        "awarded": "🟢",
        "declined": "⚫",
        "completed": "✅",
    }

    lines = [f"💰 Grants ({len(grants)}):\n"]

    for g in grants:
        emoji = status_emoji.get(g.get("status", ""), "⚪")
        amount = f"${g['amount']:,.0f}" if g.get("amount") else ""
        deadline = ""

        if g.get("deadline"):
            deadline = f" | Due: {g['deadline']}"
        elif g.get("report_due"):
            deadline = f" | Report: {g['report_due']}"

        lines.append(f"  {emoji} [{g['id']}] {g['name']}")
        if g.get("funder"):
            lines.append(f"      Funder: {g['funder']} {amount}{deadline}")

    return "\n".join(lines)


def grant_deadlines(data: dict) -> str:
    """Show upcoming grant deadlines."""
    db = _load_data()
    days = data.get("days", 60)
    cutoff = datetime.now().date() + timedelta(days=days)
    today = datetime.now().date()

    deadlines = []

    for grant in db.get("grants", []):
        for date_field in ["deadline", "report_due"]:
            if grant.get(date_field):
                try:
                    due = datetime.fromisoformat(grant[date_field]).date()
                    if due <= cutoff:
                        deadlines.append(
                            {
                                "grant": grant,
                                "date": due,
                                "type": "Application" if date_field == "deadline" else "Report",
                            }
                        )
                except (ValueError, TypeError, KeyError) as e:
                    logger.debug("Value conversion error suppressed: %s", e)
    if not deadlines:
        return f"No grant deadlines in the next {days} days."

    deadlines.sort(key=lambda x: x["date"])

    lines = ["📅 Grant Deadlines:\n"]

    for item in deadlines:
        g = item["grant"]
        days_until = (item["date"] - today).days

        if days_until < 0:
            marker = "⚠️ OVERDUE"
        elif days_until <= 7:
            marker = "🔴"
        elif days_until <= 30:
            marker = "🟡"
        else:
            marker = "🟢"

        lines.append(f"  {marker} {item['date']} - {g['name']} ({item['type']})")
        if g.get("funder"):
            lines.append(f"      {g['funder']}")

    return "\n".join(lines)


# === Quick Notes ===


def add_note(data: dict) -> str:
    """Add a quick note."""
    content = data.get("content", "").strip()
    if not content:
        return "Please provide note content."

    db = _load_data()

    note = {
        "id": _generate_id(),
        "content": content,
        "category": data.get("category", "general"),
        "created_at": datetime.now().isoformat(),
    }

    db["notes"].append(note)
    _save_data(db)

    return f"📝 Note saved [{note['id']}]"


def update_donor(data: dict) -> dict:
    """Update donor fields. Returns updated donor dict or error."""
    donor_id = data.get("id", "")
    if not donor_id:
        return {"error": "Donor ID required."}

    db = _load_data()
    donor = None
    for d in db["donors"]:
        if d["id"] == donor_id:
            donor = d
            break

    if not donor:
        return {"error": f"Donor '{donor_id}' not found."}

    _backfill_donor(donor)

    # Update allowed fields (original + AFP-standard)
    allowed = {
        "name",
        "email",
        "phone",
        "notes",
        "acquisition_source",
        "cultivation_stage",
        "stewardship_plan",
        "communication_pref",
        "preferred_channel",
        "giving_frequency",
        "org_type",
        "tax_id",
    }
    for field in allowed:
        if field in data and data[field] is not None:
            donor[field] = data[field]
    # Boolean fields
    for field in (
        "do_not_solicit",
        "do_not_email",
        "do_not_call",
        "do_not_mail",
        "is_org",
        "employer_match",
    ):
        if field in data:
            donor[field] = bool(data[field])
    if "tags" in data and isinstance(data["tags"], list):
        donor["tags"] = data["tags"]
    if "solicitation_history" in data and isinstance(data["solicitation_history"], list):
        donor["solicitation_history"] = data["solicitation_history"]

    _save_data(db)
    return donor


def delete_donor(donor_id: str) -> dict:
    """Delete a donor by ID. Returns success dict or error."""
    db = _load_data()
    before = len(db["donors"])
    db["donors"] = [d for d in db["donors"] if d["id"] != donor_id]
    if len(db["donors"]) == before:
        return {"error": f"Donor '{donor_id}' not found."}
    _save_data(db)
    return {"success": True, "id": donor_id}


def add_interaction(data: dict) -> dict:
    """Add an interaction to an existing donor. Returns updated donor or error."""
    donor_id = data.get("donor_id", "")
    if not donor_id:
        return {"error": "donor_id required."}

    db = _load_data()
    donor = None
    for d in db["donors"]:
        if d["id"] == donor_id:
            donor = d
            break

    if not donor:
        return {"error": f"Donor '{donor_id}' not found."}

    _backfill_donor(donor)

    interaction = {
        "date": data.get("date", datetime.now().isoformat()),
        "type": data.get("type", "note"),
        "amount": data.get("amount"),
        "description": data.get("description", ""),
    }
    donor["interactions"].append(interaction)

    if interaction["type"] == "gift" and interaction["amount"]:
        amount = float(interaction["amount"])
        donor["total_given"] = donor.get("total_given", 0) + amount
        donor["last_gift"] = datetime.now().isoformat()
        if not donor.get("first_gift"):
            donor["first_gift"] = datetime.now().isoformat()
        # Recompute AFP metrics
        _compute_rfm(donor)
        _compute_lybunt_sybunt(donor)

    _save_data(db)
    return donor


def update_grant(data: dict) -> dict:
    """Update grant fields. Returns updated grant dict or error."""
    grant_id = data.get("id", "")
    if not grant_id:
        return {"error": "Grant ID required."}

    db = _load_data()
    grant = None
    for g in db["grants"]:
        if g["id"] == grant_id:
            grant = g
            break

    if not grant:
        return {"error": f"Grant '{grant_id}' not found."}

    _backfill_grant(grant)

    allowed = (
        "name",
        "funder",
        "amount",
        "status",
        "deadline",
        "report_due",
        "start_date",
        "end_date",
        "notes",
        "cfda_number",
        "funder_ein",
        "opportunity_number",
        "grant_type",
        "budget_total",
        "budget_spent",
        "indirect_rate",
        "match_required",
        "match_secured",
        "reporting_schedule",
        "compliance_notes",
        "award_number",
        "program_officer",
        "program_officer_email",
        "program_officer_phone",
    )
    for field in allowed:
        if field in data and data[field] is not None:
            grant[field] = data[field]
    if "reports" in data and isinstance(data["reports"], list):
        grant["reports"] = data["reports"]

    _save_data(db)
    return grant


def delete_grant(grant_id: str) -> dict:
    """Delete a grant by ID. Returns success dict or error."""
    db = _load_data()
    before = len(db["grants"])
    db["grants"] = [g for g in db["grants"] if g["id"] != grant_id]
    if len(db["grants"]) == before:
        return {"error": f"Grant '{grant_id}' not found."}
    _save_data(db)
    return {"success": True, "id": grant_id}


def delete_note(note_id: str) -> dict:
    """Delete a note by ID. Returns success dict or error."""
    db = _load_data()
    before = len(db["notes"])
    db["notes"] = [n for n in db["notes"] if n["id"] != note_id]
    if len(db["notes"]) == before:
        return {"error": f"Note '{note_id}' not found."}
    _save_data(db)
    return {"success": True, "id": note_id}


def get_all_donors() -> list:
    """Return all donors as a list of dicts (for API use). Backfills AFP fields."""
    db = _load_data()
    donors = db.get("donors", [])
    for d in donors:
        _backfill_donor(d)
    return donors


def get_all_grants() -> list:
    """Return all grants as a list of dicts (for API use). Backfills enhanced fields."""
    db = _load_data()
    grants = db.get("grants", [])
    for g in grants:
        _backfill_grant(g)
    return grants


def get_all_notes() -> list:
    """Return all notes as a list of dicts (for API use)."""
    db = _load_data()
    return db.get("notes", [])


def search_notes(data: dict) -> str:
    """Search notes."""
    db = _load_data()
    query = data.get("query", "").lower()

    notes = db.get("notes", [])

    if query:
        notes = [n for n in notes if query in n.get("content", "").lower()]

    notes = notes[-20:]  # Last 20
    notes.reverse()

    if not notes:
        return "No notes found."

    lines = ["📝 Notes:\n"]
    for n in notes:
        date = n["created_at"][:10]
        content = n["content"][:60]
        if len(n["content"]) > 60:
            content += "..."
        lines.append(f"  [{n['id']}] {date}: {content}")

    return "\n".join(lines)


# === Legislation Tracking ===


def get_all_legislation() -> list:
    """Return all tracked bills."""
    db = _load_data()
    return db.get("legislation", [])


def add_legislation(data: dict) -> str:
    """Track a bill."""
    title = data.get("title", "").strip()
    if not title:
        return "Bill title is required."

    db = _load_data()
    bill = {
        "id": _generate_id(),
        "bill_id": data.get("bill_id", ""),
        "title": title,
        "sponsor": data.get("sponsor", ""),
        "status": data.get("status", "introduced"),
        "chamber": data.get("chamber", ""),
        "congress": data.get("congress", ""),
        "level": data.get("level", "federal"),  # federal | state
        "state": data.get("state", ""),
        "latest_action": data.get("latest_action", ""),
        "latest_action_date": data.get("latest_action_date", ""),
        "url": data.get("url", ""),
        "relevance_note": data.get("relevance_note", ""),
        "priority": data.get("priority", "watch"),  # watch | important | critical
        "created_at": datetime.now().isoformat(),
    }
    db.setdefault("legislation", []).append(bill)
    _save_data(db)
    return f"Tracking bill: {title}"


def update_legislation(data: dict) -> dict:
    """Update a tracked bill."""
    bill_id = data.get("id", "")
    if not bill_id:
        return {"error": "Bill ID required."}

    db = _load_data()
    for bill in db.get("legislation", []):
        if bill["id"] == bill_id:
            for key in (
                "title",
                "sponsor",
                "status",
                "latest_action",
                "latest_action_date",
                "relevance_note",
                "priority",
                "url",
            ):
                if key in data and data[key] is not None:
                    bill[key] = data[key]
            _save_data(db)
            return bill
    return {"error": "Bill not found."}


def delete_legislation(bill_id: str) -> bool:
    """Remove a tracked bill."""
    db = _load_data()
    before = len(db.get("legislation", []))
    db["legislation"] = [b for b in db.get("legislation", []) if b["id"] != bill_id]
    _save_data(db)
    return len(db["legislation"]) < before


def get_bill_watchlist() -> list:
    """Return keyword watchlist for bill alerts."""
    db = _load_data()
    return db.get("bill_watchlist", [])


def add_watchlist_keyword(data: dict) -> str:
    """Add a keyword/phrase to the bill watchlist."""
    keyword = data.get("keyword", "").strip().lower()
    if not keyword:
        return "Keyword is required."

    db = _load_data()
    watchlist = db.setdefault("bill_watchlist", [])

    # No duplicates
    if any(w["keyword"] == keyword for w in watchlist):
        return f"'{keyword}' is already on the watchlist."

    watchlist.append(
        {
            "id": _generate_id(),
            "keyword": keyword,
            "notify": data.get("notify", True),
            "created_at": datetime.now().isoformat(),
        }
    )
    _save_data(db)
    return f"Watching for bills matching '{keyword}'"


def remove_watchlist_keyword(keyword_id: str) -> bool:
    """Remove a keyword from the watchlist."""
    db = _load_data()
    before = len(db.get("bill_watchlist", []))
    db["bill_watchlist"] = [w for w in db.get("bill_watchlist", []) if w["id"] != keyword_id]
    _save_data(db)
    return len(db["bill_watchlist"]) < before


# Tool definitions
TOOLS = [
    {
        "name": "log_donor",
        "description": (
            "Log a donor interaction (gift, call, email, meeting) or create a new donor record. "
            "Use this to track donations, stewardship touches, and communication history. "
            "Automatically updates donor totals and timestamps when logging gifts. "
            "Creates a new donor profile if the name is not already in the database."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "name": {"type": "string", "description": "Donor name"},
                "type": {
                    "type": "string",
                    "enum": ["gift", "email", "call", "meeting", "thanks", "note"],
                    "default": "note",
                    "description": "Type of interaction to log",
                },
                "amount": {"type": "number", "description": "Gift amount (for gifts)"},
                "description": {"type": "string", "description": "Description of interaction"},
                "email": {"type": "string", "description": "Donor email address"},
                "phone": {"type": "string", "description": "Donor phone number"},
                "notes": {"type": "string", "description": "General notes about the donor"},
                "tags": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "Tags for categorizing the donor (e.g. major_donor, board_member)",
                },
            },
            "required": ["name"],
        },
        "handler": log_donor,
        "category": "nonprofit",
    },
    {
        "name": "search_donors",
        "description": (
            "Search the donor database by name, email, or minimum giving level. "
            "Use this to find specific donors or identify major contributors. "
            "Results are sorted by total giving amount, highest first. "
            "Returns donor IDs, names, totals, last gift dates, and emails."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Search name or email"},
                "min_total": {"type": "number", "description": "Minimum total giving"},
            },
        },
        "handler": search_donors,
        "category": "nonprofit",
    },
    {
        "name": "donor_details",
        "description": (
            "Get detailed information about a specific donor by ID or name. "
            "Use this to review a donor's full profile before a meeting or thank-you call. "
            "Returns contact info, giving history, total contributions, and recent interactions. "
            "Provide either the donor ID or full name for lookup."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "id": {"type": "string", "description": "Donor ID for direct lookup"},
                "name": {"type": "string", "description": "Donor full name for search"},
            },
        },
        "handler": donor_details,
        "category": "nonprofit",
    },
    {
        "name": "donors_needing_thanks",
        "description": (
            "Find donors who gave recently but have not yet been thanked. "
            "Use this for stewardship follow-ups and thank-you letter generation. "
            "Checks for gifts within the specified window with no subsequent 'thanks' interaction logged. "
            "Returns donor names, gift amounts, and contact information."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "days": {
                    "type": "integer",
                    "default": 30,
                    "description": "Look back this many days for unacknowledged gifts",
                },
            },
        },
        "handler": donors_needing_thanks,
        "category": "nonprofit",
    },
    {
        "name": "add_grant",
        "description": (
            "Add a new grant to the tracking system with funder, amount, and key dates. "
            "Use this when a new grant opportunity is identified or an application is submitted. "
            "Tracks status from prospect through completion with deadline monitoring. "
            "Grant deadlines will appear in daily briefings and deadline alerts."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "name": {"type": "string", "description": "Grant/program name"},
                "funder": {"type": "string", "description": "Funding organization"},
                "amount": {"type": "number", "description": "Grant amount in dollars"},
                "status": {
                    "type": "string",
                    "enum": ["prospect", "applied", "awarded", "declined", "completed"],
                    "description": "Current status of the grant application",
                },
                "deadline": {"type": "string", "description": "Application deadline (YYYY-MM-DD)"},
                "report_due": {"type": "string", "description": "Report due date (YYYY-MM-DD)"},
                "start_date": {
                    "type": "string",
                    "description": "Grant period start date (YYYY-MM-DD)",
                },
                "end_date": {"type": "string", "description": "Grant period end date (YYYY-MM-DD)"},
                "notes": {"type": "string", "description": "Additional notes about the grant"},
            },
            "required": ["name"],
        },
        "handler": add_grant,
        "category": "nonprofit",
    },
    {
        "name": "list_grants",
        "description": (
            "List all tracked grants with optional status filtering. "
            "Use this to review the grant pipeline or check on specific stages. "
            "Results are sorted by upcoming deadlines. "
            "Returns grant names, funders, amounts, statuses, and key dates."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "status": {
                    "type": "string",
                    "description": "Filter by status (prospect, applied, awarded, declined, completed)",
                }
            },
        },
        "handler": list_grants,
        "category": "nonprofit",
    },
    {
        "name": "grant_deadlines",
        "description": (
            "Show upcoming grant application deadlines and report due dates. "
            "Use this for weekly planning or to check what is coming up soon. "
            "Highlights overdue items and color-codes by urgency. "
            "Covers both application deadlines and grant report due dates."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "days": {
                    "type": "integer",
                    "default": 60,
                    "description": "Look ahead this many days for upcoming deadlines",
                },
            },
        },
        "handler": grant_deadlines,
        "category": "nonprofit",
    },
    {
        "name": "add_note",
        "description": (
            "Add a quick note to the nonprofit notes store. "
            "Use this for capturing brief thoughts, action items, or meeting takeaways. "
            "Notes are timestamped and can be categorized for later retrieval. "
            "Search saved notes with the search_notes tool."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "content": {"type": "string", "description": "Note content text"},
                "category": {
                    "type": "string",
                    "default": "general",
                    "description": "Category for organizing notes (e.g. general, board, fundraising)",
                },
            },
            "required": ["content"],
        },
        "handler": add_note,
        "category": "nonprofit",
    },
    {
        "name": "search_notes",
        "description": (
            "Search saved nonprofit notes by keyword. "
            "Use this to find previously captured notes, action items, or meeting takeaways. "
            "Returns the most recent 20 matching notes with dates and content previews. "
            "Omit query to list all recent notes."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "Search term to filter notes by content",
                },
            },
        },
        "handler": search_notes,
        "category": "nonprofit",
    },
]
