"""Mercury Banking API client for the Vastian Network.

Provides async access to Mercury's REST API for:
  - Account balances
  - Transaction history
  - Payment creation (with approval workflow)

Docs: https://docs.mercury.com/reference/getting-started-with-your-api
Auth: Bearer token via ``MERCURY_API_TOKEN`` env var.
"""

from __future__ import annotations

import logging
from typing import Any

import httpx

logger = logging.getLogger(__name__)

BASE_URL = "https://api.mercury.com/api/v1"


class MercuryClient:
    """Async wrapper around Mercury's banking API."""

    def __init__(
        self,
        api_token: str,
        default_account_id: str = "",
        readonly_account_ids: list[str] | None = None,
        account_names: dict[str, str] | None = None,
    ) -> None:
        self._token = api_token
        self.default_account_id = default_account_id
        self.readonly_account_ids: list[str] = readonly_account_ids or []
        self.account_names: dict[str, str] = account_names or {}

    @property
    def is_configured(self) -> bool:
        return bool(self._token)

    @property
    def all_account_ids(self) -> list[str]:
        """All configured account IDs (primary + read-only), de-duped."""
        ids: list[str] = []
        if self.default_account_id:
            ids.append(self.default_account_id)
        for aid in self.readonly_account_ids:
            if aid and aid not in ids:
                ids.append(aid)
        return ids

    def _headers(self) -> dict[str, str]:
        return {
            "Authorization": f"Bearer {self._token}",
            "Content-Type": "application/json",
            "Accept": "application/json",
        }

    # ── Accounts ──────────────────────────────────────

    async def get_accounts(self) -> list[dict[str, Any]]:
        """List all Mercury accounts."""
        async with httpx.AsyncClient(timeout=30) as client:
            resp = await client.get(f"{BASE_URL}/accounts", headers=self._headers())
            resp.raise_for_status()
            data = resp.json()
            return data.get("accounts", data) if isinstance(data, dict) else data

    async def get_account(self, account_id: str | None = None) -> dict[str, Any]:
        """Get a single account's details including balance.

        Returns dict with keys like: ``accountNumber``, ``name``, ``kind``,
        ``currentBalance``, ``availableBalance``, ``status``, etc.
        """
        aid = account_id or self.default_account_id
        if not aid:
            raise ValueError("No account_id provided and no default configured.")
        async with httpx.AsyncClient(timeout=30) as client:
            resp = await client.get(
                f"{BASE_URL}/account/{aid}",
                headers=self._headers(),
            )
            resp.raise_for_status()
            return resp.json()

    async def get_all_balances(self) -> list[dict[str, Any]]:
        """Get balances for all configured accounts.

        Tries the ``/accounts`` list endpoint first (returns everything in one
        call).  Falls back to fetching each configured account individually if
        the list endpoint doesn't return data.
        """
        # Fast path: list endpoint returns all accounts visible to the token
        try:
            accounts = await self.get_accounts()
            if accounts:
                return accounts
        except Exception:
            logger.debug("List-all accounts failed, falling back to individual", exc_info=True)

        # Slow path: fetch each configured account individually
        results: list[dict[str, Any]] = []
        for aid in self.all_account_ids:
            try:
                acct = await self.get_account(aid)
                results.append(acct)
            except Exception as e:
                results.append({"name": f"Account {aid}", "error": str(e)})
        return results

    # ── Transactions ──────────────────────────────────

    async def list_transactions(
        self,
        account_id: str | None = None,
        limit: int = 10,
        offset: int = 0,
        search: str = "",
    ) -> list[dict[str, Any]]:
        """List recent transactions for an account.

        Supports pagination and keyword search.
        """
        aid = account_id or self.default_account_id
        if not aid:
            raise ValueError("No account_id provided and no default configured.")
        params: dict[str, Any] = {"limit": limit, "offset": offset}
        if search:
            params["search"] = search
        async with httpx.AsyncClient(timeout=30) as client:
            resp = await client.get(
                f"{BASE_URL}/account/{aid}/transactions",
                headers=self._headers(),
                params=params,
            )
            resp.raise_for_status()
            data = resp.json()
            return data.get("transactions", data) if isinstance(data, dict) else data

    async def create_transaction(
        self,
        account_id: str | None = None,
        *,
        recipient_name: str,
        amount: float,
        payment_method: str = "ach",
        note: str = "",
        idempotency_key: str = "",
    ) -> dict[str, Any]:
        """Create a payment / transfer on Mercury.

        ``payment_method`` can be ``ach``, ``domesticWire``, ``internationalWire``,
        or ``check``.

        Returns the transaction object from Mercury.
        """
        aid = account_id or self.default_account_id
        if not aid:
            raise ValueError("No account_id provided and no default configured.")

        payload: dict[str, Any] = {
            "recipientName": recipient_name,
            "amount": amount,
            "paymentMethod": payment_method,
        }
        if note:
            payload["note"] = note

        headers = self._headers()
        if idempotency_key:
            headers["Idempotency-Key"] = idempotency_key

        async with httpx.AsyncClient(timeout=30) as client:
            resp = await client.post(
                f"{BASE_URL}/account/{aid}/transactions",
                headers=headers,
                json=payload,
            )
            resp.raise_for_status()
            return resp.json()

    # ── Convenience Formatters ────────────────────────

    @staticmethod
    def format_balance(account: dict[str, Any]) -> str:
        """Human-readable balance summary."""
        name = account.get("name", account.get("legalBusinessName", "Account"))
        current = account.get("currentBalance", 0)
        available = account.get("availableBalance", current)
        status = account.get("status", "unknown")
        kind = account.get("kind", "")
        return (
            f"**{name}** ({kind})\n"
            f"  Current balance: ${current:,.2f}\n"
            f"  Available: ${available:,.2f}\n"
            f"  Status: {status}"
        )

    def format_all_balances(
        self,
        accounts: list[dict[str, Any]],
        primary_id: str = "",
    ) -> str:
        """Human-readable multi-account balance summary.

        Uses ``self.account_names`` to label accounts with user-assigned names
        (e.g., 'Travel Checking', 'Emergency fund').
        """
        if not accounts:
            return "No accounts found."
        lines: list[str] = []
        total_current = 0.0
        total_available = 0.0
        for acct in accounts:
            if "error" in acct:
                lines.append(f"**{acct.get('name', 'Unknown')}** — Error: {acct['error']}")
                continue
            api_name = acct.get("name", acct.get("legalBusinessName", "Account"))
            acct_id = str(acct.get("id", acct.get("accountNumber", "")))
            # Use user-assigned name if available, otherwise API name
            display_name = self.account_names.get(acct_id, api_name)
            kind = acct.get("kind", "")
            current = acct.get("currentBalance", 0)
            available = acct.get("availableBalance", current)
            status = acct.get("status", "unknown")
            tag = " **(primary — read/write)**" if acct_id == str(primary_id) else " *(read-only)*"
            lines.append(
                f"**{display_name}** ({kind}){tag}\n"
                f"  ID: {acct_id} | Current: ${current:,.2f} | Available: ${available:,.2f} | Status: {status}"
            )
            total_current += current
            total_available += available
        lines.append(
            f"\n**Total across all accounts**\n"
            f"  Current: ${total_current:,.2f} | Available: ${total_available:,.2f}"
        )
        return "\n\n".join(lines)

    @staticmethod
    def format_transactions(txns: list[dict[str, Any]], limit: int = 10) -> str:
        """Human-readable transaction list."""
        if not txns:
            return "No transactions found."
        lines = []
        for t in txns[:limit]:
            amt = t.get("amount", 0)
            kind = t.get("kind", "?")
            status = t.get("status", "?")
            counterparty = t.get("counterpartyName", t.get("counterpartyNickname", "Unknown"))
            date = (t.get("postedAt") or t.get("createdAt") or "")[:10]
            note = t.get("note", "")
            sign = "+" if amt >= 0 else ""
            line = f"  {date} | {sign}${amt:,.2f} | {kind} | {counterparty} | {status}"
            if note:
                line += f" | {note[:40]}"
            lines.append(line)
        return "\n".join(lines)
