"""QuidClaw CLI — AI-friendly interface to Beancount operations."""

import json
import os
import shutil
import sys
from importlib import resources
from pathlib import Path

import click

from quidclaw.config import QuidClawConfig
from quidclaw.core.ledger import Ledger


def get_config() -> QuidClawConfig:
    """Get config from current directory or QUIDCLAW_DATA_DIR."""
    data_dir = os.environ.get("QUIDCLAW_DATA_DIR", os.getcwd())
    return QuidClawConfig(data_dir)


def get_ledger() -> Ledger:
    """Get initialized ledger."""
    config = get_config()
    ledger = Ledger(config)
    if not config.main_bean.exists():
        click.echo("Error: No ledger found. Run 'quidclaw init' first.", err=True)
        sys.exit(1)
    return ledger


def _try_backup(config: QuidClawConfig, message: str) -> None:
    """Attempt git backup if initialized. Never raises."""
    try:
        from quidclaw.core.backup import try_backup
        try_backup(config, message)
    except Exception:
        pass


@click.group()
@click.version_option(version="0.3.0")
def main():
    """QuidClaw — Zero-barrier personal CFO powered by Beancount."""
    pass


# --- Setup ---


PLATFORMS = ["openclaw", "claude-code", "gemini", "codex"]


@main.command()
@click.option("--platform", type=click.Choice(PLATFORMS), default=None,
              help="Target platform (openclaw, claude-code, gemini, codex)")
def init(platform):
    """Initialize a new financial project in the current directory."""
    if platform is None:
        click.echo("Which platform are you using?")
        click.echo("  1. OpenClaw (recommended)")
        click.echo("  2. Claude Code")
        click.echo("  3. Gemini CLI")
        click.echo("  4. Codex")
        click.echo("  5. Other")
        choice = click.prompt("", type=click.IntRange(1, 5))
        platform = {1: "openclaw", 2: "claude-code", 3: "gemini",
                    4: "codex", 5: "codex"}[choice]

    config = get_config()
    ledger = Ledger(config)
    ledger.init()

    # Copy workflow files
    workflows_dir = Path(__file__).parent / "workflows"
    target_dir = Path(config.data_dir) / ".quidclaw" / "workflows"
    if workflows_dir.exists():
        target_dir.mkdir(parents=True, exist_ok=True)
        for f in workflows_dir.glob("*.md"):
            shutil.copy2(f, target_dir / f.name)

    # Store platform choice
    config.set_setting("platform", platform)

    # Generate platform-specific files
    body = _build_instruction_body(config)
    data_dir = Path(config.data_dir)

    if platform == "openclaw":
        from quidclaw.core.openclaw import OpenClawSetup
        setup = OpenClawSetup(config)
        setup.generate_templates()
        setup.generate_agents_md(body)

        # Auto-enable git backup
        from quidclaw.core.backup import BackupManager
        mgr = BackupManager(config)
        if mgr.is_git_available() and not mgr.is_initialized():
            mgr.init()
            click.echo("Git backup: initialized")

        # Try to create OpenClaw agent
        if setup.is_available():
            if setup.create_agent():
                click.echo("OpenClaw agent 'quidclaw' created.")
            else:
                click.echo("Warning: Could not create OpenClaw agent.", err=True)
                click.echo(f"  Run: openclaw agents add quidclaw --workspace {config.data_dir}", err=True)
        else:
            click.echo("Note: openclaw CLI not found. After installing, run:")
            click.echo(f"  openclaw agents add quidclaw --workspace {config.data_dir}")

    elif platform == "claude-code":
        (data_dir / "CLAUDE.md").write_text(body)
        click.echo("Created CLAUDE.md")

    elif platform == "gemini":
        (data_dir / "GEMINI.md").write_text(body)
        click.echo("Created GEMINI.md")

    elif platform == "codex":
        (data_dir / "AGENTS.md").write_text(body)
        click.echo("Created AGENTS.md")

    click.echo(f"Initialized QuidClaw project in {config.data_dir}")
    _try_backup(config, "Initialize QuidClaw data directory")


@main.command()
def upgrade():
    """Upgrade workflow files and instruction files to latest version."""
    config = get_config()

    workflows_dir = Path(__file__).parent / "workflows"
    target_dir = Path(config.data_dir) / ".quidclaw" / "workflows"
    if workflows_dir.exists():
        target_dir.mkdir(parents=True, exist_ok=True)
        for f in workflows_dir.glob("*.md"):
            shutil.copy2(f, target_dir / f.name)
        click.echo(f"Updated workflows in {target_dir}")

    if config.main_bean.exists():
        ledger = Ledger(config)
        ledger.ensure_dirs()

    platform = config.get_setting("platform", "claude-code")
    body = _build_instruction_body(config)
    data_dir = Path(config.data_dir)

    if platform == "openclaw":
        from quidclaw.core.openclaw import OpenClawSetup
        setup = OpenClawSetup(config)
        setup.generate_templates()
        setup.generate_agents_md(body)
        click.echo("Updated OpenClaw files")
    elif platform == "claude-code":
        (data_dir / "CLAUDE.md").write_text(body)
        click.echo("Updated CLAUDE.md")
    elif platform == "gemini":
        (data_dir / "GEMINI.md").write_text(body)
        click.echo("Updated GEMINI.md")
    elif platform == "codex":
        (data_dir / "AGENTS.md").write_text(body)
        click.echo("Updated AGENTS.md")

    # Update SKILL.md if it exists (for ClawHub users)
    skill_path = data_dir / "skills" / "quidclaw" / "SKILL.md"
    if skill_path.exists():
        skill_prefix = (
            "---\nname: quidclaw\n"
            "description: Personal CFO — AI-powered financial management via Beancount\n"
            "metadata:\n  openclaw:\n    requires:\n      bins: [quidclaw]\n---\n\n"
            "> **Recommended:** For the best experience, create a dedicated QuidClaw\n"
            "> agent with `quidclaw init --platform openclaw`.\n\n"
        )
        skill_path.write_text(skill_prefix + body)
        click.echo("Updated skills/quidclaw/SKILL.md")

    click.echo("Upgrade complete.")
    _try_backup(config, "Upgrade QuidClaw workflows")


@main.command("set-config")
@click.argument("key")
@click.argument("value")
def set_config(key, value):
    """Set a configuration value."""
    config = get_config()
    config.set_setting(key, value)

    # If setting operating_currency, also update main.bean
    if key == "operating_currency" and config.main_bean.exists():
        content = config.main_bean.read_text()
        if 'option "operating_currency"' in content:
            import re
            content = re.sub(
                r'option "operating_currency" ".*?"',
                f'option "operating_currency" "{value}"',
                content,
            )
        else:
            # Insert after the title line
            content = content.replace(
                'option "title" "QuidClaw Ledger"\n',
                f'option "title" "QuidClaw Ledger"\n'
                f'option "operating_currency" "{value}"\n',
            )
        config.main_bean.write_text(content)

    click.echo(f"Set {key} = {value}")
    _try_backup(config, f"Update config: {key}")


@main.command("get-config")
@click.argument("key", required=False)
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
def get_config_cmd(key, as_json):
    """Get configuration values."""
    config = get_config()
    if key:
        value = config.get_setting(key)
        if as_json:
            click.echo(json.dumps({key: value}))
        else:
            click.echo(f"{key}: {value}" if value is not None else f"{key}: (not set)")
    else:
        settings = config.load_settings()
        if as_json:
            click.echo(json.dumps(settings, indent=2))
        else:
            if not settings:
                click.echo("No settings configured.")
            for k, v in settings.items():
                click.echo(f"{k}: {v}")


@main.command()
def setup():
    """Create default accounts using configured operating currency."""
    from quidclaw.core.init import LedgerInitializer
    ledger = get_ledger()
    operating = ledger.config.get_setting("operating_currency")
    if not operating:
        click.echo("Error: operating_currency not set. Run 'quidclaw set-config operating_currency CNY' first.", err=True)
        sys.exit(1)
    initializer = LedgerInitializer(ledger)
    created = initializer.init_with_template()
    if created:
        click.echo(f"Created {len(created)} default accounts ({operating})")
        _try_backup(ledger.config, f"Set up default accounts ({operating})")
    else:
        click.echo("All default accounts already exist.")


# --- Ledger Operations ---


@main.command("add-account")
@click.argument("name")
@click.option("--currencies", default=None, help="Comma-separated currencies")
@click.option("--date", "open_date", default=None, help="Open date (YYYY-MM-DD)")
def add_account(name, currencies, open_date):
    """Open a new account."""
    from quidclaw.core.accounts import AccountManager
    ledger = get_ledger()
    mgr = AccountManager(ledger)
    currency_list = [c.strip() for c in currencies.split(",")] if currencies else None
    mgr.add_account(name, currency_list, open_date)
    click.echo(f"Opened account {name}")
    _try_backup(ledger.config, f"Add account: {name}")


@main.command("close-account")
@click.argument("name")
@click.option("--date", "close_date", default=None, help="Close date (YYYY-MM-DD)")
def close_account(name, close_date):
    """Close an account."""
    from quidclaw.core.accounts import AccountManager
    ledger = get_ledger()
    mgr = AccountManager(ledger)
    mgr.close_account(name, close_date)
    click.echo(f"Closed account {name}")
    _try_backup(ledger.config, f"Close account: {name}")


@main.command("list-accounts")
@click.option("--type", "account_type", default=None, help="Filter by type (Assets, Liabilities, etc.)")
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
def list_accounts(account_type, as_json):
    """List all accounts."""
    from quidclaw.core.accounts import AccountManager
    ledger = get_ledger()
    mgr = AccountManager(ledger)
    accounts = mgr.list_accounts(account_type)
    if as_json:
        click.echo(json.dumps(accounts, indent=2))
    else:
        for acc in accounts:
            click.echo(acc)


@main.command("add-txn")
@click.option("--date", required=True, help="Transaction date (YYYY-MM-DD)")
@click.option("--payee", required=True, help="Payee name")
@click.option("--narration", default="", help="Description")
@click.option("--posting", multiple=True, required=True, help='Posting as JSON: \'{"account":"...", "amount":"...", "currency":"..."}\'')
@click.option("--meta", default=None, help='Metadata as JSON: \'{"source":"..."}\'')
def add_txn(date, payee, narration, posting, meta):
    """Record a transaction."""
    import datetime as dt
    from quidclaw.core.transactions import TransactionManager
    ledger = get_ledger()
    mgr = TransactionManager(ledger)
    postings = [json.loads(p) for p in posting]
    parsed_date = dt.date.fromisoformat(date)
    metadata = json.loads(meta) if meta else None
    mgr.add_transaction(parsed_date, payee, narration, postings, metadata)
    click.echo(f"Recorded transaction: {date} {payee}")
    _try_backup(ledger.config, f"Add transaction: {payee}")


@main.command()
@click.option("--account", default=None, help="Specific account (omit for all)")
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
def balance(account, as_json):
    """Query account balances."""
    from quidclaw.core.balance import BalanceManager
    ledger = get_ledger()
    mgr = BalanceManager(ledger)
    if account:
        result = mgr.get_balance(account)
    else:
        result = mgr.get_all_balances()
    if as_json:
        # Convert Decimal to str for JSON
        serializable = {k: {ck: str(cv) for ck, cv in v.items()} if isinstance(v, dict) else str(v)
                       for k, v in result.items()}
        click.echo(json.dumps(serializable, indent=2))
    else:
        for k, v in result.items():
            click.echo(f"{k}: {v}")


@main.command("balance-check")
@click.argument("account")
@click.argument("expected")
@click.option("--currency", default=None, help="Currency (defaults to operating currency)")
def balance_check(account, expected, currency):
    """Reconciliation: assert an account balance."""
    from decimal import Decimal
    from quidclaw.core.balance import BalanceManager
    ledger = get_ledger()
    if currency is None:
        currency = ledger.config.get_setting("operating_currency", "CNY")
    mgr = BalanceManager(ledger)
    ok, message = mgr.balance_check(account, Decimal(expected), currency)
    click.echo(message)
    if ok:
        _try_backup(ledger.config, f"Balance assertion: {account}")
    if not ok:
        sys.exit(1)


@main.command()
@click.argument("bql")
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
def query(bql, as_json):
    """Execute a BQL query."""
    from quidclaw.core.reports import ReportManager
    ledger = get_ledger()
    mgr = ReportManager(ledger)
    columns, rows = mgr.query(bql)
    if as_json:
        result = [dict(zip(columns, [str(v) for v in row])) for row in rows]
        click.echo(json.dumps(result, indent=2))
    else:
        click.echo("\t".join(columns))
        for row in rows:
            click.echo("\t".join(str(v) for v in row))


@main.command()
@click.argument("report_type", type=click.Choice(["income", "balance_sheet"]))
@click.option("--period", default=None, help="Period filter")
def report(report_type, period):
    """Generate a financial report."""
    from quidclaw.core.reports import ReportManager
    ledger = get_ledger()
    mgr = ReportManager(ledger)
    if report_type == "income":
        result = mgr.income_statement(period)
    else:
        result = mgr.balance_sheet()
    click.echo(result)


@main.command("monthly-summary")
@click.argument("year", type=int)
@click.argument("month", type=int)
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
def monthly_summary(year, month, as_json):
    """Income, expenses, and savings for a month."""
    from quidclaw.core.reports import ReportManager
    ledger = get_ledger()
    mgr = ReportManager(ledger)
    result = mgr.monthly_summary(year, month)
    if as_json:
        serializable = {k: str(v) for k, v in result.items()}
        click.echo(json.dumps(serializable, indent=2))
    else:
        for k, v in result.items():
            click.echo(f"{k}: {v}")


@main.command("spending-by-category")
@click.argument("year", type=int)
@click.argument("month", type=int)
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
def spending_by_category(year, month, as_json):
    """Ranked category breakdown for a month."""
    from quidclaw.core.reports import ReportManager
    ledger = get_ledger()
    mgr = ReportManager(ledger)
    result = mgr.spending_by_category(year, month)
    if as_json:
        click.echo(json.dumps(result, indent=2, default=str))
    else:
        for item in result:
            click.echo(f"{item['category']}: {item['amount']} {item['currency']}")


@main.command("month-comparison")
@click.argument("year", type=int)
@click.argument("month", type=int)
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
def month_comparison(year, month, as_json):
    """Month-over-month comparison with percentages."""
    from quidclaw.core.reports import ReportManager
    ledger = get_ledger()
    mgr = ReportManager(ledger)
    result = mgr.month_over_month(year, month)
    if as_json:
        click.echo(json.dumps(result, indent=2, default=str))
    else:
        for item in result:
            sign = "+" if item["change_pct"] > 0 else ""
            click.echo(f"{item['category']}: {item['current']} {item['currency']} (prev: {item['previous']}, {sign}{item['change_pct']}%)")


@main.command("largest-txns")
@click.argument("year", type=int)
@click.argument("month", type=int)
@click.option("--limit", default=10, help="Number of transactions")
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
def largest_txns(year, month, limit, as_json):
    """Top N largest expense transactions."""
    from quidclaw.core.reports import ReportManager
    ledger = get_ledger()
    mgr = ReportManager(ledger)
    result = mgr.largest_transactions(year, month, limit)
    if as_json:
        click.echo(json.dumps(result, indent=2, default=str))
    else:
        for item in result:
            click.echo(f"{item['date']} {item['payee']}: {item['amount']} {item['currency']} ({item['account']})")


@main.command("detect-anomalies")
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
def detect_anomalies(as_json):
    """Run all anomaly checks."""
    from quidclaw.core.anomaly import AnomalyDetector
    ledger = get_ledger()
    detector = AnomalyDetector(ledger)
    results = {
        "duplicates": detector.find_duplicate_charges(),
        "recurring": detector.find_recurring_charges(),
        "price_changes": detector.find_price_changes(),
        "outliers": detector.find_large_outliers(),
        "unknown_merchants": detector.find_unknown_merchants(),
    }
    if as_json:
        click.echo(json.dumps(results, indent=2, default=str))
    else:
        for category, items in results.items():
            if items:
                click.echo(f"\n## {category}")
                for item in items:
                    click.echo(f"  - {item}")


@main.command("data-status")
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
def data_status(as_json):
    """Data freshness: inbox count, last ledger update."""
    from quidclaw.core.inbox import InboxManager
    config = get_config()
    mgr = InboxManager(config)
    status = mgr.get_data_status()
    status["sources"] = mgr.get_source_status()

    if as_json:
        click.echo(json.dumps(status, indent=2, default=str))
    else:
        click.echo(f"Inbox files: {status.get('inbox_count', 0)}")
        for f in status.get('inbox_files', []):
            click.echo(f"  - {f}")
        click.echo(f"Last modified: {status.get('last_modified', 'N/A')}")
        source_status = status.get("sources", {})
        if source_status:
            click.echo("\nData sources:")
            for name, info in source_status.items():
                last = info.get("last_sync") or "never"
                click.echo(f"  {name} ({info['provider']}): last sync {last}")


@main.command("add-commodity")
@click.argument("name")
@click.option("--source", required=True, help='Price source (e.g., "yahoo/AAPL")')
@click.option("--quote", default="USD", help="Quote currency (default: USD)")
@click.option("--date", "open_date", default=None, help="Date (YYYY-MM-DD)")
def add_commodity(name, source, quote, open_date):
    """Register a commodity (stock, fund, custom asset) for price tracking."""
    import datetime as dt
    from quidclaw.core.prices import PriceManager
    ledger = get_ledger()
    mgr = PriceManager(ledger)
    date = dt.date.fromisoformat(open_date) if open_date else None
    try:
        mgr.add_commodity(name, source, quote, date)
    except ValueError as e:
        click.echo(f"Error: {e}", err=True)
        sys.exit(1)
    click.echo(f"Registered commodity {name} ({quote}:{source})")
    _try_backup(ledger.config, f"Add commodity: {name}")


@main.command("add-source")
@click.argument("name")
@click.option("--provider", required=True, help="Provider type (e.g., agentmail)")
@click.option("--api-key", default=None, help="API key for the provider")
@click.option("--inbox-id", default=None, help="Inbox/mailbox ID (provider-specific)")
@click.option("--username", default=None, help="Preferred username for new inbox")
@click.option("--display-name", default=None, help="Display name for the inbox")
def add_source(name, provider, api_key, inbox_id, username, display_name):
    """Add a new data source."""
    try:
        import quidclaw.core.sources.agentmail  # noqa: F401 — registers provider
    except ImportError:
        pass
    from quidclaw.core.sources.registry import create_source
    config = get_config()
    source_config = {"provider": provider, "enabled": True}
    if api_key:
        source_config["api_key"] = api_key
    if inbox_id:
        source_config["inbox_id"] = inbox_id
    if username:
        source_config["username"] = username
    if display_name:
        source_config["display_name"] = display_name

    try:
        source = create_source(name, source_config, config)
        source_config = source.provision()
    except ImportError as e:
        click.echo(f"Error: {e}", err=True)
        sys.exit(1)

    config.add_source(name, source_config)
    config.source_dir(name).mkdir(parents=True, exist_ok=True)

    inbox = source_config.get("inbox_id", "")
    click.echo(f"Added source '{name}' (provider: {provider})")
    if inbox:
        click.echo(f"  Inbox: {inbox}")
    _try_backup(config, f"Add data source: {name}")


@main.command("list-sources")
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
def list_sources(as_json):
    """List configured data sources."""
    config = get_config()
    sources = config.get_sources()
    if as_json:
        click.echo(json.dumps(sources, indent=2, default=str))
    else:
        if not sources:
            click.echo("No data sources configured.")
            return
        for name, src in sources.items():
            enabled = src.get("enabled", True)
            status_str = "enabled" if enabled else "disabled"
            click.echo(f"  {name}: {src['provider']} ({status_str})")
            if src.get("inbox_id"):
                click.echo(f"    inbox: {src['inbox_id']}")


@main.command("remove-source")
@click.argument("name")
@click.option("--confirm", is_flag=True, help="Confirm removal")
def remove_source(name, confirm):
    """Remove a data source configuration."""
    if not confirm:
        click.echo("Error: Use --confirm to remove a source.", err=True)
        sys.exit(1)
    config = get_config()
    try:
        config.remove_source(name)
    except KeyError as e:
        click.echo(f"Error: {e}", err=True)
        sys.exit(1)
    data_dir = config.source_dir(name)
    click.echo(f"Removed source '{name}' from config.")
    if data_dir.exists():
        click.echo(f"  Synced data preserved at: {data_dir}")
        click.echo(f"  Delete manually if no longer needed.")
    _try_backup(config, f"Remove data source: {name}")


@main.command("sync")
@click.argument("source_name", required=False)
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
def sync(source_name, as_json):
    """Sync data from external sources."""
    try:
        import quidclaw.core.sources.agentmail  # noqa: F401 — registers provider
    except ImportError:
        pass
    from quidclaw.core.sources.registry import create_source
    config = get_config()
    sources = config.get_sources()

    if not sources:
        click.echo("No data sources configured. Use 'quidclaw add-source' first.")
        sys.exit(1)

    if source_name:
        src_config = config.get_source(source_name)
        if not src_config:
            click.echo(f"Error: Source '{source_name}' not found.", err=True)
            sys.exit(1)
        to_sync = {source_name: src_config}
    else:
        to_sync = {n: s for n, s in sources.items() if s.get("enabled", True)}

    all_results = []
    for name, src_config in to_sync.items():
        source = create_source(name, src_config, config)
        result = source.sync()
        all_results.append(result)
        if not as_json:
            if result.items_fetched > 0:
                click.echo(f"  {name}: {result.items_fetched} new item(s)")
            else:
                click.echo(f"  {name}: up to date")
            for err in result.errors:
                click.echo(f"    ERROR: {err}", err=True)

    if as_json:
        output = [
            {
                "source_name": r.source_name,
                "provider": r.provider,
                "items_fetched": r.items_fetched,
                "items_stored": r.items_stored,
                "last_sync": r.last_sync.isoformat() if r.last_sync else None,
                "errors": r.errors,
            }
            for r in all_results
        ]
        click.echo(json.dumps(output, indent=2))

    total = sum(r.items_fetched for r in all_results)
    if total > 0:
        sources_str = ", ".join(r.source_name for r in all_results if r.items_fetched > 0)
        _try_backup(config, f"Sync: {total} new items from {sources_str}")

    has_errors = any(r.errors for r in all_results)
    has_items = any(r.items_fetched > 0 for r in all_results)
    if has_errors and not has_items:
        sys.exit(1)


@main.command("mark-processed")
@click.argument("source_name")
@click.argument("email_dir")
def mark_processed(source_name, email_dir):
    """Mark an email as processed."""
    config = get_config()
    import yaml as _yaml
    email_path = config.source_dir(source_name) / email_dir
    envelope_file = email_path / "envelope.yaml"
    if not envelope_file.exists():
        click.echo(f"Error: envelope.yaml not found at {envelope_file}", err=True)
        sys.exit(1)
    envelope = _yaml.safe_load(envelope_file.read_text())
    envelope["status"] = "processed"
    envelope_file.write_text(
        _yaml.dump(envelope, default_flow_style=False, allow_unicode=True)
    )
    click.echo(f"Marked {email_dir} as processed")
    _try_backup(config, f"Mark processed: {source_name}/{email_dir}")


@main.command("fetch-prices")
@click.argument("commodities", nargs=-1)
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
def fetch_prices(commodities, as_json):
    """Fetch and record asset prices from configured sources."""
    from quidclaw.core.prices import PriceManager
    ledger = get_ledger()
    mgr = PriceManager(ledger)
    try:
        results = mgr.fetch_prices(list(commodities) if commodities else None)
    except (ValueError, ImportError) as e:
        click.echo(f"Error: {e}", err=True)
        sys.exit(1)
    if as_json:
        click.echo(json.dumps(results, indent=2))
    else:
        for r in results:
            if "error" in r:
                click.echo(f"  {r['commodity']}: ERROR — {r['error']}", err=True)
            else:
                click.echo(f"  {r['commodity']}: {r['price']} {r['currency']} ({r['date']})")
    _try_backup(ledger.config, "Fetch prices")


# --- Backup ---


@main.group()
def backup():
    """Git backup management."""
    pass


@backup.command("init")
def backup_init():
    """Initialize Git backup for this data directory."""
    from quidclaw.core.backup import BackupManager
    config = get_config()
    mgr = BackupManager(config)
    if not mgr.is_git_available():
        click.echo(f"Error: Git is not installed. {mgr.get_install_instructions()}", err=True)
        sys.exit(1)
    if mgr.is_initialized():
        click.echo("Git backup already initialized.")
        return
    mgr.init()
    click.echo("Initialized Git backup.")
    if not mgr.is_lfs_available():
        click.echo("Note: git-lfs not installed. Binary files will be stored without LFS.")
        click.echo("  Install: brew install git-lfs  (or: apt install git-lfs)")


@backup.command("status")
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
def backup_status(as_json):
    """Show backup status."""
    from quidclaw.core.backup import BackupManager
    config = get_config()
    mgr = BackupManager(config)
    status = mgr.status()
    if as_json:
        click.echo(json.dumps(status, indent=2))
    else:
        if not status["initialized"]:
            click.echo("Git backup: not initialized")
            click.echo("  Run 'quidclaw backup init' to enable.")
            return
        click.echo("Git backup: initialized")
        click.echo(f"  Last commit: {status['last_commit']}")
        click.echo(f"  LFS: {'available' if status['lfs_available'] else 'not installed'}")
        remotes = status["remotes"]
        if remotes:
            click.echo(f"  Remotes ({len(remotes)}):")
            for r in remotes:
                click.echo(f"    {r['name']}: {r['url']}")
        else:
            click.echo("  Remotes: none configured")


@backup.command("add-remote")
@click.argument("name")
@click.argument("url")
def backup_add_remote(name, url):
    """Add a remote repository for backup."""
    from quidclaw.core.backup import BackupManager
    config = get_config()
    mgr = BackupManager(config)
    if not mgr.is_initialized():
        click.echo("Error: Git backup not initialized. Run 'quidclaw backup init' first.", err=True)
        sys.exit(1)
    mgr.add_remote(name, url)
    click.echo(f"Added remote '{name}': {url}")
    click.echo("  Make sure the repository is Private (financial data!).")
    click.echo(f"  Test with: quidclaw backup push --remote {name}")


@backup.command("remove-remote")
@click.argument("name")
def backup_remove_remote(name):
    """Remove a remote repository."""
    import subprocess as _sp
    from quidclaw.core.backup import BackupManager
    config = get_config()
    mgr = BackupManager(config)
    if not mgr.is_initialized():
        click.echo("Error: Git backup not initialized.", err=True)
        sys.exit(1)
    try:
        mgr.remove_remote(name)
    except _sp.CalledProcessError:
        click.echo(f"Error: Remote '{name}' not found.", err=True)
        sys.exit(1)
    click.echo(f"Removed remote '{name}'.")


@backup.command("push")
@click.option("--remote", default=None, help="Push to specific remote (default: all)")
def backup_push(remote):
    """Push to remote repositories."""
    import subprocess as _sp
    from quidclaw.core.backup import BackupManager
    config = get_config()
    mgr = BackupManager(config)
    if not mgr.is_initialized():
        click.echo("Error: Git backup not initialized.", err=True)
        sys.exit(1)
    if remote:
        try:
            mgr._run_git("push", remote)
            click.echo(f"Pushed to '{remote}'.")
        except _sp.CalledProcessError as e:
            click.echo(f"Error pushing to '{remote}': {e.stderr.strip()}", err=True)
            sys.exit(1)
    else:
        remotes = mgr.list_remotes()
        if not remotes:
            click.echo("No remotes configured. Use 'quidclaw backup add-remote' first.", err=True)
            sys.exit(1)
        for r in remotes:
            try:
                mgr._run_git("push", r["name"])
                click.echo(f"Pushed to '{r['name']}'.")
            except _sp.CalledProcessError as e:
                click.echo(f"Error pushing to '{r['name']}': {e.stderr.strip()}", err=True)


# --- Helpers ---


def _build_instruction_body(config: QuidClawConfig) -> str:
    """Build the shared instruction body used by all platform instruction files."""
    currency = config.get_setting("operating_currency")
    currency_line = (
        f"- Operating currency: {currency}"
        if currency
        else "- Operating currency: (not yet configured — will be set during onboarding)"
    )
    return f"""\
# QuidClaw — Personal CFO

You are a personal CFO managing finances in this directory.
Speak the user's language. Never mention beancount, double-entry, or accounting jargon.

## First Thing to Do

When you start a conversation, check:
1. Read `.quidclaw/config.yaml`. If `operating_currency` is missing → this is a new user. Read `.quidclaw/workflows/onboarding.md` and start the onboarding interview.
2. Are there files in `inbox/`? If YES → mention them and offer to process.
3. Otherwise → greet the user and ask how you can help.

## Configuration

{currency_line}
- Config file: `.quidclaw/config.yaml`

## Directory Structure

- `ledger/` — Beancount ledger files (structured, verified data only)
- `inbox/` — Drop zone for unprocessed files (bank statements, receipts)
- `documents/` — Organized archive (by year/month)
- `notes/` — Financial knowledge base (living documents + append-only logs)
- `reports/` — Generated reports
- `sources/` — Synced data from external sources (email, APIs)
- `logs/` — Processing audit trail

## Available CLI Commands

Run these in the shell for Beancount engine operations:

```
# Setup
quidclaw init                        # Initialize ledger structure
quidclaw set-config KEY VALUE        # Set a configuration value
quidclaw get-config [KEY]            # Read configuration
quidclaw setup                       # Create default accounts (requires operating_currency)
quidclaw upgrade                     # Update workflows to latest version

# Accounts
quidclaw add-account NAME [--currencies X]  # Open account
quidclaw close-account NAME          # Close account
quidclaw list-accounts [--type X]    # List accounts

# Transactions
quidclaw add-txn --date D --payee P --posting '{{...}}'  # Record transaction

# Queries & Reports
quidclaw balance [--account X]       # Query balances
quidclaw balance-check ACCT AMT      # Reconciliation assertion
quidclaw query "SELECT ..."          # Execute BQL query
quidclaw report income|balance_sheet # Financial reports
quidclaw monthly-summary YYYY MM     # Monthly income/expenses/savings
quidclaw spending-by-category YYYY MM # Category breakdown
quidclaw month-comparison YYYY MM    # Month-over-month changes
quidclaw largest-txns YYYY MM        # Top expenses
quidclaw detect-anomalies            # Find duplicates, outliers, etc.
quidclaw data-status                 # Inbox count, last ledger update

# Price Tracking
quidclaw add-commodity NAME --source SOURCE --quote CURRENCY  # Register price source
quidclaw fetch-prices [COMMODITY...] # Fetch prices for registered commodities

# Data Sources
quidclaw add-source NAME --provider PROVIDER [--api-key KEY]  # Add data source
quidclaw list-sources                    # List configured sources
quidclaw remove-source NAME --confirm    # Remove a data source
quidclaw sync [SOURCE]                   # Sync from external sources
quidclaw mark-processed SOURCE DIR       # Mark email as processed

# Backup
quidclaw backup init                    # Initialize Git backup
quidclaw backup status                  # Show backup status
quidclaw backup add-remote NAME URL     # Add remote for backup
quidclaw backup remove-remote NAME      # Remove a remote
quidclaw backup push [--remote NAME]    # Push to remotes
```

### Price Tracking

When you encounter a new currency, crypto, or investment asset, register it with `add-commodity`:

```
# Fiat currencies — ticker format: {{BASE}}{{QUOTE}}=X
quidclaw add-commodity USD --source yahoo/USDCNY=X --quote CNY

# Crypto — ticker format: {{BASE}}-{{QUOTE}}
quidclaw add-commodity BTC --source yahoo/BTC-CNY --quote CNY

# Stocks/funds — ticker is the symbol, quote is trading currency
quidclaw add-commodity AAPL --source yahoo/AAPL --quote USD
quidclaw add-commodity 600519 --source yahoo/600519.SS --quote CNY
```

Then `quidclaw fetch-prices` will fetch all registered prices automatically.

### Source Traceability

When recording transactions from imported files or emails, include source metadata:
```
quidclaw add-txn ... --meta '{{"source":"email:source-name/email-dir","import-id":"evt_ID"}}'
```
This enables tracing any transaction back to its source document.

Most commands support `--json` for structured output.

## File Operations

For file operations, use your native tools directly:
- Read/write notes in `notes/*.md`
- List inbox: `inbox/*`
- Search notes across `notes/`
- List documents: `documents/**/*`

## Workflows

Read `.quidclaw/workflows/<name>.md` for detailed workflow instructions:
- `onboarding.md` — New user setup
- `import-bills.md` — Parse and import financial documents
- `reconcile.md` — Data accuracy check (run before any report)
- `monthly-review.md` — Generate monthly financial review
- `detect-anomalies.md` — Scan for suspicious patterns
- `organize-documents.md` — Sort inbox files into documents/
- `financial-memory.md` — Capture non-transaction financial info
- `check-email.md` — Check and process email sources
- `daily-routine.md` — Run daily financial routine

## Notes Structure

- **Living documents** (profile.md, calendar.md, assets/, accounts/, etc.) — always reflect current state
- **Append-only logs** (decisions/, journal/) — historical record, only grows

## Conventions

- Only verified data (bank statements, receipts) goes into the ledger
- Transactions go into monthly files: `ledger/YYYY/YYYY-MM.bean`
- Document naming: `{{Source}}-{{Type}}-{{YYYY-MM}}.{{ext}}`
- Account naming: use last 4 digits or identifiers (e.g., Assets:Bank:CMB:1234)
- Always reconcile before generating reports or answering financial questions
"""
