# 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

"""
Reports / Export Skill

Generate formatted data exports and reports from existing skill data.
Board packets, donor summaries, financial snapshots, grant status reports.

Dependencies:
  - openpyxl (XLSX generation) -- optional, falls back to CSV
  - csv (stdlib)
"""

import csv
import json
import logging
from datetime import datetime
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"


try:
    from familiar.core.utils import generate_id
except ImportError:
    generate_id = None

logger = logging.getLogger(__name__)

try:
    import openpyxl
    from openpyxl.styles import Border, Font, PatternFill, Side
    from openpyxl.utils import get_column_letter

    OPENPYXL_AVAILABLE = True
except ImportError:
    OPENPYXL_AVAILABLE = False

DATA_DIR = _get_data_dir()
OUTPUT_DIR = Path.home() / ".familiar" / "documents"


def _ensure_output():
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)


def _load_json(filename):
    path = DATA_DIR / filename
    if path.exists():
        try:
            with open(path, encoding="utf-8") as f:
                return json.load(f)
        except (json.JSONDecodeError, IOError) as e:
            logger.debug("JSON parse error suppressed: %s", e)
    return {}


def _output_path(name, ext):
    _ensure_output()
    ts = datetime.now().strftime("%Y%m%d_%H%M%S")
    safe = "".join(c if c.isalnum() or c in "_-" else "_" for c in name)[:50]
    return OUTPUT_DIR / f"{safe}_{ts}.{ext}"


def _write_csv(filepath, headers, rows):
    with open(filepath, "w", newline="", encoding="utf-8") as f:
        writer = csv.writer(f)
        writer.writerow(headers)
        writer.writerows(rows)


def _write_xlsx(filepath, sheets):
    """Write XLSX with multiple sheets. sheets: {name: {headers, rows, column_widths, add_totals}}"""
    if not OPENPYXL_AVAILABLE:
        return False
    wb = openpyxl.Workbook()
    header_font = Font(bold=True, size=11)
    header_fill = PatternFill(start_color="D5E8F0", end_color="D5E8F0", fill_type="solid")
    thin_border = Border(
        left=Side(style="thin", color="CCCCCC"),
        right=Side(style="thin", color="CCCCCC"),
        top=Side(style="thin", color="CCCCCC"),
        bottom=Side(style="thin", color="CCCCCC"),
    )
    first = True
    for sheet_name, sd in sheets.items():
        ws = wb.active if first else wb.create_sheet(sheet_name)
        if first:
            ws.title = sheet_name
            first = False
        headers = sd.get("headers", [])
        rows = sd.get("rows", [])
        for ci, h in enumerate(headers, 1):
            cell = ws.cell(row=1, column=ci, value=h)
            cell.font = header_font
            cell.fill = header_fill
            cell.border = thin_border
        for ri, row in enumerate(rows, 2):
            for ci, val in enumerate(row, 1):
                cell = ws.cell(row=ri, column=ci, value=val)
                cell.border = thin_border
                if (
                    isinstance(val, (int, float))
                    and ci <= len(headers)
                    and any(
                        kw in headers[ci - 1].lower()
                        for kw in ["amount", "total", "income", "expense", "balance"]
                    )
                ):
                    cell.number_format = "#,##0.00"
        for ci in range(1, len(headers) + 1):
            ml = len(str(headers[ci - 1])) if ci <= len(headers) else 10
            for row in rows[:50]:
                if ci <= len(row):
                    ml = max(ml, len(str(row[ci - 1])))
            ws.column_dimensions[get_column_letter(ci)].width = min(ml + 4, 40)
        ws.freeze_panes = "A2"
        if rows and sd.get("add_totals"):
            tr = len(rows) + 2
            ws.cell(row=tr, column=1, value="TOTAL").font = Font(bold=True)
            for ci, h in enumerate(headers, 1):
                if any(kw in h.lower() for kw in ["amount", "total", "income", "expense"]):
                    total = sum(
                        r[ci - 1]
                        for r in rows
                        if ci - 1 < len(r) and isinstance(r[ci - 1], (int, float))
                    )
                    c = ws.cell(row=tr, column=ci, value=total)
                    c.font = Font(bold=True)
                    c.number_format = "#,##0.00"
    wb.save(str(filepath))
    return True


# === Tool Handlers ===


def donor_report(data):
    np_data = _load_json("nonprofit.json")
    donors = np_data.get("donors", [])
    if not donors:
        return "No donor data found."
    fmt = data.get("format", "xlsx").lower()
    period = data.get("period")
    headers = [
        "Name",
        "Email",
        "Total Given",
        "First Gift",
        "Last Gift",
        "Gift Count",
        "Tier",
        "Tags",
    ]
    rows = []
    for d in donors:
        total = d.get("total_given", 0)
        gifts = [i for i in d.get("interactions", []) if i.get("type") == "gift"]
        if period:
            gifts = [g for g in gifts if g.get("date", "").startswith(period)]
            total = sum(float(g.get("amount", 0)) for g in gifts)
        if total >= 10000:
            tier = "Major ($10K+)"
        elif total >= 1000:
            tier = "Mid-Level ($1K+)"
        elif total >= 100:
            tier = "Supporter ($100+)"
        elif total > 0:
            tier = "Donor"
        else:
            tier = "Prospect"
        rows.append(
            [
                d.get("name", ""),
                d.get("email", ""),
                total,
                d.get("first_gift", "")[:10] if d.get("first_gift") else "",
                d.get("last_gift", "")[:10] if d.get("last_gift") else "",
                len(gifts),
                tier,
                ", ".join(d.get("tags", [])),
            ]
        )
    rows.sort(key=lambda r: r[2], reverse=True)
    fn = f"donor_report{'_' + period if period else ''}"
    if fmt == "xlsx" and OPENPYXL_AVAILABLE:
        fp = _output_path(fn, "xlsx")
        _write_xlsx(fp, {"Donor Summary": {"headers": headers, "rows": rows, "add_totals": True}})
        return f"✅ Donor report: {fp} ({len(rows)} donors)"
    fp = _output_path(fn, "csv")
    _write_csv(fp, headers, rows)
    return f"✅ Donor report: {fp} ({len(rows)} donors)"


def grant_report(data):
    np_data = _load_json("nonprofit.json")
    grants = np_data.get("grants", [])
    if not grants:
        return "No grant data found."
    fmt = data.get("format", "xlsx").lower()
    headers = [
        "Name",
        "Funder",
        "Status",
        "Amount",
        "Deadline",
        "Report Due",
        "Start",
        "End",
        "Notes",
    ]
    so = {"prospect": 0, "applied": 1, "awarded": 2, "completed": 3, "declined": 4}
    rows = []
    for g in grants:
        rows.append(
            [
                g.get("name", ""),
                g.get("funder", ""),
                g.get("status", "prospect"),
                g.get("amount", 0) or 0,
                g.get("deadline", ""),
                g.get("report_due", ""),
                g.get("start_date", ""),
                g.get("end_date", ""),
                g.get("notes", "")[:60],
            ]
        )
    rows.sort(key=lambda r: so.get(r[2], 9))
    if fmt == "xlsx" and OPENPYXL_AVAILABLE:
        fp = _output_path("grant_report", "xlsx")
        _write_xlsx(fp, {"Grant Pipeline": {"headers": headers, "rows": rows, "add_totals": True}})
        return f"✅ Grant report: {fp} ({len(rows)} grants)"
    fp = _output_path("grant_report", "csv")
    _write_csv(fp, headers, rows)
    return f"✅ Grant report: {fp} ({len(rows)} grants)"


def financial_snapshot(data):
    bk = _load_json("bookkeeping.json")
    txs = bk.get("transactions", [])
    if not txs:
        return "No bookkeeping data found."
    start = data.get("start_date", f"{datetime.now().year}-01-01")
    end = data.get("end_date", datetime.now().strftime("%Y-%m-%d"))
    fmt = data.get("format", "xlsx").lower()
    filtered = [t for t in txs if start <= t.get("date", "") <= end]
    if not filtered:
        return f"No transactions between {start} and {end}."
    inc_rows, exp_rows = [], []
    dh = ["Date", "Source/Vendor", "Category", "Amount", "Fund", "Description"]
    for t in filtered:
        row = [
            t.get("date", ""),
            t.get("source") or t.get("vendor", ""),
            t.get("category", ""),
            t["amount"],
            t.get("fund", "General"),
            t.get("description", "")[:50],
        ]
        (inc_rows if t["type"] == "income" else exp_rows).append(row)
    ti, te = sum(r[3] for r in inc_rows), sum(r[3] for r in exp_rows)
    if fmt == "xlsx" and OPENPYXL_AVAILABLE:
        fp = _output_path("financial_snapshot", "xlsx")
        _write_xlsx(
            fp,
            {
                "Summary": {
                    "headers": ["Metric", "Amount"],
                    "rows": [["Total Income", ti], ["Total Expenses", te], ["Net", ti - te]],
                },
                "Income": {"headers": dh, "rows": inc_rows, "add_totals": True},
                "Expenses": {"headers": dh, "rows": exp_rows, "add_totals": True},
            },
        )
        return f"✅ Financial snapshot: {fp} ({len(filtered)} tx, {start} to {end})"
    fp = _output_path("financial_snapshot", "csv")
    _write_csv(
        fp, dh, [["INCOME"]] + inc_rows + [[""], ["EXPENSES"]] + exp_rows + [[""], ["Net", ti - te]]
    )
    return f"✅ Financial snapshot: {fp}"


def export_to_csv(data):
    dataset = data.get("dataset", "").strip().lower()
    dm = {
        "donors": ("nonprofit.json", "donors"),
        "grants": ("nonprofit.json", "grants"),
        "contacts": ("contacts.json", "contacts"),
        "tasks": ("tasks.json", "tasks"),
        "transactions": ("bookkeeping.json", "transactions"),
    }
    if dataset not in dm:
        return f"Unknown dataset. Available: {', '.join(dm.keys())}"
    fn, key = dm[dataset]
    records = _load_json(fn).get(key, [])
    if not records:
        return f"No {dataset} data found."
    if isinstance(records[0], dict):
        headers = [k for k in records[0].keys() if not isinstance(records[0][k], dict)]
        rows = []
        for r in records:
            rows.append(
                [
                    "; ".join(str(v) for v in r[h]) if isinstance(r.get(h), list) else r.get(h, "")
                    for h in headers
                ]
            )
    else:
        headers, rows = ["value"], [[r] for r in records]
    fp = _output_path(f"{dataset}_export", "csv")
    _write_csv(fp, headers, rows)
    return f"✅ Exported {len(rows)} {dataset} to {fp}"


def export_to_xlsx(data):
    global openpyxl, Font, PatternFill, Border, Side, get_column_letter, OPENPYXL_AVAILABLE
    if not OPENPYXL_AVAILABLE:
        from familiar.core.deps import XLSX_PACKAGES, ensure_packages

        ok, _ = ensure_packages(XLSX_PACKAGES)
        if ok:
            import openpyxl  # noqa: F811
            from openpyxl.styles import Border, Font, PatternFill, Side  # noqa: F811
            from openpyxl.utils import get_column_letter  # noqa: F811

            OPENPYXL_AVAILABLE = True
        else:
            return "❌ XLSX export not available. Use export_to_csv instead."
    dataset = data.get("dataset", "").strip().lower()
    dm = {
        "donors": ("nonprofit.json", "donors"),
        "grants": ("nonprofit.json", "grants"),
        "contacts": ("contacts.json", "contacts"),
        "tasks": ("tasks.json", "tasks"),
        "transactions": ("bookkeeping.json", "transactions"),
    }
    if dataset not in dm:
        return f"Unknown dataset. Available: {', '.join(dm.keys())}"
    fn, key = dm[dataset]
    records = _load_json(fn).get(key, [])
    if not records:
        return f"No {dataset} data found."
    if isinstance(records[0], dict):
        headers = [k for k in records[0].keys() if not isinstance(records[0][k], dict)]
        rows = [
            [
                "; ".join(str(v) for v in r[h]) if isinstance(r.get(h), list) else r.get(h, "")
                for h in headers
            ]
            for r in records
        ]
    else:
        headers, rows = ["value"], [[r] for r in records]
    fp = _output_path(f"{dataset}_export", "xlsx")
    _write_xlsx(fp, {dataset.title(): {"headers": headers, "rows": rows, "add_totals": True}})
    return f"✅ Exported {len(rows)} {dataset} to {fp}"


def board_packet(data):
    fmt = data.get("format", "xlsx").lower()
    md = data.get("meeting_date", datetime.now().strftime("%B %d, %Y"))
    ps = data.get("period_start", f"{datetime.now().year}-01-01")
    pe = data.get("period_end", datetime.now().strftime("%Y-%m-%d"))
    bk = _load_json("bookkeeping.json")
    ftx = [t for t in bk.get("transactions", []) if ps <= t.get("date", "") <= pe]
    ti = sum(t["amount"] for t in ftx if t["type"] == "income")
    te = sum(t["amount"] for t in ftx if t["type"] == "expense")
    ic, ec = {}, {}
    for t in ftx:
        d = ic if t["type"] == "income" else ec
        d[t.get("category", "other")] = d.get(t.get("category", "other"), 0) + t["amount"]
    np_data = _load_json("nonprofit.json")
    donors = sorted(np_data.get("donors", []), key=lambda x: x.get("total_given", 0), reverse=True)[
        :20
    ]
    dr = [
        [
            d.get("name", ""),
            d.get("total_given", 0),
            d.get("last_gift", "")[:10] if d.get("last_gift") else "",
            len([i for i in d.get("interactions", []) if i.get("type") == "gift"]),
        ]
        for d in donors
    ]
    grants = np_data.get("grants", [])
    gr = [
        [
            g.get("name", ""),
            g.get("funder", ""),
            g.get("status", ""),
            g.get("amount", 0) or 0,
            g.get("deadline", ""),
        ]
        for g in grants
    ]
    tasks = _load_json("tasks.json").get("tasks", [])
    ot = [t for t in tasks if t.get("status") != "done"][:15]
    tr = [
        [
            t.get("title", "")[:60],
            t.get("priority", "normal"),
            t.get("due_date", ""),
            t.get("status", "pending"),
        ]
        for t in ot
    ]
    if fmt == "xlsx" and OPENPYXL_AVAILABLE:
        fp = _output_path("board_packet", "xlsx")
        _write_xlsx(
            fp,
            {
                "Financial Summary": {
                    "headers": ["Metric", "Amount"],
                    "rows": [
                        ["Total Income", ti],
                        ["Total Expenses", te],
                        ["Net", ti - te],
                        ["Transactions", len(ftx)],
                    ],
                },
                "Income Detail": {
                    "headers": ["Category", "Amount"],
                    "rows": [[c, a] for c, a in sorted(ic.items(), key=lambda x: -x[1])],
                    "add_totals": True,
                },
                "Expense Detail": {
                    "headers": ["Category", "Amount"],
                    "rows": [[c, a] for c, a in sorted(ec.items(), key=lambda x: -x[1])],
                    "add_totals": True,
                },
                "Top Donors": {
                    "headers": ["Name", "Total Given", "Last Gift", "Gifts"],
                    "rows": dr,
                    "add_totals": True,
                },
                "Grant Pipeline": {
                    "headers": ["Name", "Funder", "Status", "Amount", "Deadline"],
                    "rows": gr,
                    "add_totals": True,
                },
                "Open Actions": {"headers": ["Task", "Priority", "Due", "Status"], "rows": tr},
            },
        )
        return f"✅ Board packet: {fp}\n   Meeting: {md} | Period: {ps} to {pe}\n   Income: ${ti:,.2f} | Expense: ${te:,.2f} | Net: ${ti - te:,.2f}"
    fp = _output_path("board_packet", "csv")
    with open(fp, "w", newline="", encoding="utf-8") as f:
        w = csv.writer(f)
        w.writerow([f"BOARD PACKET - {md}"])
        w.writerow([f"Period: {ps} to {pe}"])
        w.writerow([])
        w.writerow(["FINANCIALS"])
        w.writerows([["Income", ti], ["Expenses", te], ["Net", ti - te]])
        w.writerow([])
        w.writerow(["TOP DONORS", "Total", "Last Gift", "Gifts"])
        w.writerows(dr)
        w.writerow([])
        w.writerow(["GRANTS", "Funder", "Status", "Amount", "Deadline"])
        w.writerows(gr)
    return f"✅ Board packet: {fp}"


# === Tool Definitions ===

TOOLS = [
    {
        "name": "donor_report",
        "description": (
            "Generate a donor summary report with total giving, donor tiers, and gift counts. "
            "Use this when the user needs a formatted donor report filtered by period. "
            "Outputs as XLSX (with styling and totals) or CSV, saved to ~/.familiar/documents/."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "format": {
                    "type": "string",
                    "enum": ["xlsx", "csv"],
                    "default": "xlsx",
                    "description": "Output file format (xlsx or csv)",
                },
                "period": {"type": "string", "description": "Filter by year (e.g. '2025')"},
            },
        },
        "handler": donor_report,
        "category": "reports",
    },
    {
        "name": "grant_report",
        "description": (
            "Generate a grant pipeline status report showing deadlines, amounts, and statuses. "
            "Use this when the user wants an overview of all tracked grants sorted by status. "
            "Outputs as XLSX (with totals row) or CSV, saved to ~/.familiar/documents/."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "format": {
                    "type": "string",
                    "enum": ["xlsx", "csv"],
                    "default": "xlsx",
                    "description": "Output file format (xlsx or csv)",
                },
            },
        },
        "handler": grant_report,
        "category": "reports",
    },
    {
        "name": "financial_snapshot",
        "description": (
            "Generate an income/expense financial snapshot from bookkeeping transactions. "
            "Use this when the user needs a financial summary for a date range. "
            "Produces a multi-sheet XLSX (Summary, Income, Expenses) or a single CSV file."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "start_date": {"type": "string", "description": "Start date (YYYY-MM-DD)"},
                "end_date": {"type": "string", "description": "End date (YYYY-MM-DD)"},
                "format": {
                    "type": "string",
                    "enum": ["xlsx", "csv"],
                    "default": "xlsx",
                    "description": "Output file format (xlsx or csv)",
                },
            },
        },
        "handler": financial_snapshot,
        "category": "reports",
    },
    {
        "name": "export_to_csv",
        "description": (
            "Export any stored dataset to a CSV file for external use. "
            "Supported datasets: donors, grants, contacts, tasks, transactions. "
            "Returns the file path of the exported CSV in ~/.familiar/documents/."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "dataset": {
                    "type": "string",
                    "enum": ["donors", "grants", "contacts", "tasks", "transactions"],
                    "description": "Which dataset to export",
                }
            },
            "required": ["dataset"],
        },
        "handler": export_to_csv,
        "category": "reports",
    },
    {
        "name": "export_to_xlsx",
        "description": (
            "Export any stored dataset to a formatted Excel workbook with styled headers and totals. "
            "Supported datasets: donors, grants, contacts, tasks, transactions. "
            "Requires openpyxl; returns an error message if the package is unavailable."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "dataset": {
                    "type": "string",
                    "enum": ["donors", "grants", "contacts", "tasks", "transactions"],
                    "description": "Which dataset to export",
                }
            },
            "required": ["dataset"],
        },
        "handler": export_to_xlsx,
        "category": "reports",
    },
    {
        "name": "board_packet",
        "description": (
            "Generate a complete board meeting packet combining financial summary, top donors, "
            "grant pipeline, and open action items into one document. "
            "Use this before board meetings to prepare a comprehensive multi-sheet XLSX or CSV report. "
            "Covers a configurable reporting period and includes income/expense breakdowns."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "meeting_date": {"type": "string", "description": "Meeting date (default: today)"},
                "period_start": {
                    "type": "string",
                    "description": "Reporting period start (YYYY-MM-DD)",
                },
                "period_end": {
                    "type": "string",
                    "description": "Reporting period end (YYYY-MM-DD)",
                },
                "format": {
                    "type": "string",
                    "enum": ["xlsx", "csv"],
                    "default": "xlsx",
                    "description": "Output file format (xlsx or csv)",
                },
            },
        },
        "handler": board_packet,
        "category": "reports",
    },
]
