# 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


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:
                return json.load(f)
        except (json.JSONDecodeError, IOError, OSError) as e:
            logger.warning(f"Failed to load nonprofit data: {e}")
    return {"donors": [], "grants": [], "notes": [], "compliance": []}


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))


# === 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", []),
        }
        db["donors"].append(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"]

    _save_data(db)

    if interaction["type"] == "gift":
        return f"✅ Logged ${interaction['amount']} gift from {name}. Total: ${donor['total_given']:.2f}"
    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):
                pass

        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):
                pass

        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"),  # prospect, applied, awarded, declined, completed
        "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(),
    }

    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):
                    pass

    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 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)


# 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",
    },
]
