import datetime
from decimal import Decimal
from mcp.server.fastmcp import FastMCP, Context
from mcp.server.fastmcp.exceptions import ToolError


def register_tools(mcp: FastMCP):

    # === Setup ===

    @mcp.tool()
    async def set_data_dir(ctx: Context, path: str) -> str:
        """Set the QuidClaw data directory. Call this at session start with the user's
        working directory. If the directory has an existing ledger/, it will be loaded.
        If not, a new ledger will be initialized."""
        from pathlib import Path
        app = ctx.request_context.lifespan_context
        p = Path(path).expanduser().resolve()
        if not p.exists():
            raise ToolError(f"Directory does not exist: {path}")
        app.activate(p)
        has_ledger = (p / "ledger" / "main.bean").exists()
        if has_ledger:
            return f"QuidClaw activated: {p} (existing ledger loaded)"
        else:
            return f"QuidClaw activated: {p} (new ledger initialized)"

    # === Ledger Operations ===

    @mcp.tool()
    async def init_ledger(ctx: Context, accounts: list[dict] | None = None) -> str:
        """Initialize ledger with account template. Pass accounts from AI conversation or omit for defaults."""
        from quidclaw.core.init import LedgerInitializer
        app = ctx.request_context.lifespan_context
        initializer = LedgerInitializer(app.ledger)
        created = initializer.init_with_template(accounts=accounts)
        if not created:
            return "Ledger already initialized, no new accounts created."
        return f"Created {len(created)} accounts:\n" + "\n".join(f"  - {a}" for a in created)

    @mcp.tool()
    async def add_account(
        ctx: Context, name: str, currencies: list[str] | None = None, open_date: str | None = None
    ) -> str:
        """Open a new account. Example: name='Assets:Bank:BOC', currencies=['CNY']"""
        from quidclaw.core.accounts import AccountManager
        app = ctx.request_context.lifespan_context
        mgr = AccountManager(app.ledger)
        date = datetime.date.fromisoformat(open_date) if open_date else None
        mgr.add_account(name, currencies=currencies, open_date=date)
        return f"Account opened: {name}"

    @mcp.tool()
    async def close_account(ctx: Context, name: str, close_date: str | None = None) -> str:
        """Close an account."""
        from quidclaw.core.accounts import AccountManager
        app = ctx.request_context.lifespan_context
        mgr = AccountManager(app.ledger)
        date = datetime.date.fromisoformat(close_date) if close_date else None
        mgr.close_account(name, close_date=date)
        return f"Account closed: {name}"

    @mcp.tool()
    async def list_accounts(ctx: Context, type: str | None = None) -> str:
        """List all accounts, optionally filtered by type (Assets, Liabilities, Income, Expenses, Equity)."""
        from quidclaw.core.accounts import AccountManager
        app = ctx.request_context.lifespan_context
        mgr = AccountManager(app.ledger)
        accounts = mgr.list_accounts(account_type=type)
        if not accounts:
            return "No accounts found."
        return "\n".join(accounts)

    @mcp.tool()
    async def add_transaction(
        ctx: Context,
        date: str,
        payee: str,
        narration: str,
        postings: list[dict],
    ) -> str:
        """Add a transaction. Each posting: {account, amount (optional), currency (optional)}.
        Omit amount on one posting for auto-balance."""
        from quidclaw.core.transactions import TransactionManager
        app = ctx.request_context.lifespan_context
        mgr = TransactionManager(app.ledger)
        txn_date = datetime.date.fromisoformat(date)
        mgr.add_transaction(date=txn_date, payee=payee, narration=narration, postings=postings)
        return f"Transaction recorded: {date} {payee} - {narration}"

    @mcp.tool()
    async def get_balance(ctx: Context, account: str | None = None, date: str | None = None) -> str:
        """Get account balance. Omit account for all balances."""
        from quidclaw.core.balance import BalanceManager
        app = ctx.request_context.lifespan_context
        mgr = BalanceManager(app.ledger)
        if account:
            bal = mgr.get_balance(account)
            if not bal:
                return f"{account}: no balance"
            parts = [f"{amount} {curr}" for curr, amount in bal.items()]
            return f"{account}: {', '.join(parts)}"
        else:
            all_bal = mgr.get_all_balances()
            if not all_bal:
                return "No balances found."
            lines = []
            for acct, currencies in sorted(all_bal.items()):
                parts = [f"{amount} {curr}" for curr, amount in currencies.items()]
                lines.append(f"{acct}: {', '.join(parts)}")
            return "\n".join(lines)

    @mcp.tool()
    async def balance_check(
        ctx: Context, account: str, expected_amount: str, currency: str, date: str | None = None
    ) -> str:
        """Check if account balance matches expected amount (for reconciliation)."""
        from quidclaw.core.balance import BalanceManager
        app = ctx.request_context.lifespan_context
        mgr = BalanceManager(app.ledger)
        ok, msg = mgr.balance_check(account, Decimal(expected_amount), currency)
        return msg

    @mcp.tool()
    async def query(ctx: Context, bql: str) -> str:
        """Execute a BQL (Beancount Query Language) query. Returns tabular results."""
        from quidclaw.core.reports import ReportManager
        app = ctx.request_context.lifespan_context
        mgr = ReportManager(app.ledger)
        try:
            columns, rows = mgr.query(bql)
        except Exception as e:
            raise ToolError(f"BQL query error: {e}")
        if not rows:
            return "No results."
        lines = [" | ".join(str(c) for c in columns)]
        lines.append("-" * len(lines[0]))
        for row in rows:
            lines.append(" | ".join(str(v) for v in row))
        return "\n".join(lines)

    @mcp.tool()
    async def report(ctx: Context, type: str, period: str | None = None) -> str:
        """Generate a financial report. Types: income_statement, balance_sheet."""
        from quidclaw.core.reports import ReportManager
        app = ctx.request_context.lifespan_context
        mgr = ReportManager(app.ledger)
        if type == "income_statement":
            return mgr.income_statement(period=period)
        elif type == "balance_sheet":
            return mgr.balance_sheet()
        else:
            raise ToolError(f"Unknown report type: {type}. Use: income_statement, balance_sheet")

    @mcp.tool()
    async def fetch_prices(ctx: Context, commodities: list[str] | None = None) -> str:
        """Fetch latest prices for commodities and write to ledger."""
        from quidclaw.core.prices import PriceManager
        app = ctx.request_context.lifespan_context
        mgr = PriceManager(app.ledger)
        try:
            results = mgr.fetch_prices(commodities)
            return "\n".join(f"{r['commodity']}: {r['price']} {r['currency']}" for r in results)
        except NotImplementedError as e:
            raise ToolError(str(e))

    # === Reports & Analysis ===

    @mcp.tool()
    async def monthly_summary(ctx: Context, year: int, month: int) -> str:
        """Get income, expenses, and savings for a specific month."""
        from quidclaw.core.reports import ReportManager
        app = ctx.request_context.lifespan_context
        mgr = ReportManager(app.ledger)
        result = mgr.monthly_summary(year, month)
        lines = [f"Monthly Summary: {year}-{month:02d}\n"]
        for label, data in [("Income", result["income"]), ("Expenses", result["expenses"]), ("Savings", result["savings"])]:
            if data:
                parts = [f"{amt} {curr}" for curr, amt in data.items()]
                lines.append(f"  {label}: {', '.join(parts)}")
            else:
                lines.append(f"  {label}: (none)")
        return "\n".join(lines)

    @mcp.tool()
    async def spending_by_category(ctx: Context, year: int, month: int) -> str:
        """Get spending breakdown by category for a month, sorted by amount."""
        from quidclaw.core.reports import ReportManager
        app = ctx.request_context.lifespan_context
        mgr = ReportManager(app.ledger)
        result = mgr.spending_by_category(year, month)
        if not result:
            return f"No spending data for {year}-{month:02d}."
        lines = [f"Spending by Category: {year}-{month:02d}\n"]
        for i, r in enumerate(result, 1):
            lines.append(f"  {i}. {r['category']}: {r['amount']} {r['currency']}")
        return "\n".join(lines)

    @mcp.tool()
    async def month_comparison(ctx: Context, year: int, month: int) -> str:
        """Compare spending by category with previous month. Shows changes."""
        from quidclaw.core.reports import ReportManager
        app = ctx.request_context.lifespan_context
        mgr = ReportManager(app.ledger)
        result = mgr.month_over_month(year, month)
        if not result:
            return f"No data to compare for {year}-{month:02d}."
        lines = [f"Month-over-Month: {year}-{month:02d} vs previous\n"]
        for r in result:
            sign = "+" if r["change_pct"] >= 0 else ""
            lines.append(f"  {r['category']}: {r['current']} {r['currency']} ({sign}{r['change_pct']}%)")
        return "\n".join(lines)

    @mcp.tool()
    async def largest_transactions(ctx: Context, year: int, month: int, limit: int = 5) -> str:
        """Get the N largest expense transactions for a month."""
        from quidclaw.core.reports import ReportManager
        app = ctx.request_context.lifespan_context
        mgr = ReportManager(app.ledger)
        result = mgr.largest_transactions(year, month, limit=limit)
        if not result:
            return f"No transactions for {year}-{month:02d}."
        lines = [f"Largest Transactions: {year}-{month:02d}\n"]
        for i, r in enumerate(result, 1):
            lines.append(f"  {i}. {r['date']} {r['payee']} — {r['amount']} {r['currency']}")
        return "\n".join(lines)

    @mcp.tool()
    async def detect_anomalies(ctx: Context) -> str:
        """Scan all transactions for anomalies: duplicates, subscriptions, price changes, outliers, unknown merchants."""
        from quidclaw.core.anomaly import AnomalyDetector
        app = ctx.request_context.lifespan_context
        detector = AnomalyDetector(app.ledger)
        sections = []

        dupes = detector.find_duplicate_charges()
        if dupes:
            lines = ["Possible Duplicate Charges:"]
            for d in dupes:
                lines.append(f"  - {d['txn_a']['date']} vs {d['txn_b']['date']}: {d['txn_a']['payee']} {d['txn_a']['amount']} {d['txn_a']['currency']}")
            sections.append("\n".join(lines))

        recurring = detector.find_recurring_charges()
        if recurring:
            lines = ["Recurring Subscriptions:"]
            for r in recurring:
                lines.append(f"  - {r['payee']}: ~{r['avg_amount']} {r['currency']}/month ({r['occurrences']} months)")
            sections.append("\n".join(lines))

        changes = detector.find_price_changes()
        if changes:
            lines = ["Price Changes:"]
            for c in changes:
                lines.append(f"  - {c['payee']}: {c['old_amount']} -> {c['new_amount']} {c['currency']} (from {c['old_month']})")
            sections.append("\n".join(lines))

        outliers = detector.find_large_outliers()
        if outliers:
            lines = ["Unusually Large Transactions:"]
            for o in outliers:
                lines.append(f"  - {o['date']} {o['payee']}: {o['amount']} {o['currency']} ({o['multiple']}x category average)")
            sections.append("\n".join(lines))

        unknown = detector.find_unknown_merchants()
        if unknown:
            lines = ["Unknown/One-time Merchants:"]
            for u in unknown:
                lines.append(f"  - {u['date']} {u['payee']}: {u['amount']} {u['currency']}")
            sections.append("\n".join(lines))

        if not sections:
            return "No anomalies detected."
        return "\n\n".join(sections)
