#!/usr/bin/env python3
"""
fetch-balances.py — Plaid balance fetcher for CLAWSTIN widget.

Reads credentials from plaid-credentials.json, fetches account balances
via Plaid API, and writes results to .cache/balances.json.

Run on a schedule via LaunchAgent (hourly). No interactive prompts.
"""

import json
import os
import sys
from datetime import datetime, timezone

# Paths (relative to workspace root)
WORKSPACE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
CREDS_FILE = os.path.join(WORKSPACE, "plaid-credentials.json")
CACHE_DIR  = os.path.join(WORKSPACE, ".cache")
CACHE_FILE = os.path.join(CACHE_DIR, "balances.json")


def write_cache(data: dict):
    os.makedirs(CACHE_DIR, exist_ok=True)
    with open(CACHE_FILE, "w") as f:
        json.dump(data, f, indent=2)


def write_error(message: str):
    write_cache({
        "error": message,
        "updated_at": datetime.now(timezone.utc).isoformat(),
    })
    print(f"[fetch-balances] ERROR: {message}", file=sys.stderr)


def load_credentials():
    if not os.path.exists(CREDS_FILE):
        raise FileNotFoundError(
            f"Credentials file not found: {CREDS_FILE}\n"
            f"Copy plaid-credentials.json.template to plaid-credentials.json and fill in your values."
        )
    with open(CREDS_FILE) as f:
        creds = json.load(f)
    required = ["client_id", "secret", "access_tokens", "environment"]
    for key in required:
        if key not in creds:
            raise ValueError(f"Missing required key in credentials: {key}")
    return creds


def fetch_balances():
    try:
        creds = load_credentials()
    except (FileNotFoundError, ValueError, json.JSONDecodeError) as e:
        write_error(str(e))
        return

    # Import plaid here so missing library gives a clean error
    try:
        from plaid.api import plaid_api
        from plaid.model.accounts_balance_get_request import AccountsBalanceGetRequest
        from plaid import Configuration, ApiClient, Environment
    except ImportError:
        write_error("plaid-python library not installed. Run: pip install plaid-python")
        return

    env_map = {
        "production": Environment.Production,
        "sandbox":    Environment.Sandbox,
        "development": Environment.Development,
    }
    env_str = creds.get("environment", "production").lower()
    plaid_env = env_map.get(env_str, Environment.Production)

    configuration = Configuration(
        host=plaid_env,
        api_key={
            "clientId": creds["client_id"],
            "secret":   creds["secret"],
        }
    )

    accounts_out = []
    errors = []

    with ApiClient(configuration) as api_client:
        client = plaid_api.PlaidApi(api_client)

        token_labels = {
            "bofa_personal":    "BofA Personal",
            "brixton_branding": "Brixton Branding",
        }

        for token_key, display_name in token_labels.items():
            access_token = creds["access_tokens"].get(token_key)
            if not access_token or access_token == "ACCESS_TOKEN_HERE":
                errors.append(f"No valid access token for {token_key} — skipping")
                print(f"[fetch-balances] Skipping {token_key}: no token configured", file=sys.stderr)
                continue

            try:
                req = AccountsBalanceGetRequest(access_token=access_token)
                response = client.accounts_balance_get(req)
                for acct in response.accounts:
                    bal = acct.balances.current
                    if bal is None:
                        bal = acct.balances.available or 0.0
                    acct_type = str(acct.subtype) if acct.subtype else "unknown"
                    # Strip "AccountSubtype." prefix if present
                    acct_type = acct_type.replace("AccountSubtype.", "")
                    accounts_out.append({
                        "name":    f"{display_name} — {acct.name}",
                        "balance": round(float(bal), 2),
                        "type":    acct_type,
                    })
            except Exception as e:
                err_msg = f"{token_key}: {type(e).__name__}: {e}"
                errors.append(err_msg)
                print(f"[fetch-balances] ERROR fetching {token_key}: {e}", file=sys.stderr)

    total = round(sum(a["balance"] for a in accounts_out), 2)
    now   = datetime.now(timezone.utc).isoformat()

    result = {
        "updated_at": now,
        "accounts":   accounts_out,
        "total":      total,
    }
    if errors:
        result["partial_errors"] = errors

    write_cache(result)
    print(f"[fetch-balances] Done. {len(accounts_out)} accounts, total=${total}, updated {now}")


if __name__ == "__main__":
    fetch_balances()
