"""Plaid API client for the Vastian Network.

Provides async-compatible access to Plaid for:
  - Account balances
  - Transaction history

Docs: https://plaid.com/docs/api/
Auth: client_id + secret from dashboard; access_token from Link exchange.
"""

from __future__ import annotations

import asyncio
import logging
from datetime import date, timedelta
from typing import Any

logger = logging.getLogger(__name__)

_PLAID_AVAILABLE = False
try:
    import plaid
    from plaid.api import plaid_api
    from plaid.model.accounts_balance_get_request import AccountsBalanceGetRequest
    from plaid.model.transactions_get_request import TransactionsGetRequest
    from plaid.model.transactions_get_request_options import TransactionsGetRequestOptions

    _PLAID_AVAILABLE = True
except ImportError:
    pass


def _run_in_executor(coro_fn, *args, **kwargs):
    """Run a synchronous Plaid call in a thread to avoid blocking."""

    def _run():
        return coro_fn(*args, **kwargs)

    return asyncio.get_event_loop().run_in_executor(None, _run)


class PlaidClient:
    """Async-compatible wrapper around Plaid API for balances and transactions."""

    def __init__(
        self,
        client_id: str,
        secret: str,
        environment: str = "sandbox",
        access_tokens: list[str] | None = None,
    ) -> None:
        if not _PLAID_AVAILABLE:
            raise ImportError("plaid-python not installed. Run: pip install vastian-network[plaid]")
        self._client_id = client_id
        self._secret = secret
        self._env_map = {
            "sandbox": plaid.Environment.Sandbox,
            "development": plaid.Environment.Development,
            "production": plaid.Environment.Production,
        }
        self._environment = self._env_map.get(environment.lower(), plaid.Environment.Sandbox)
        self._access_tokens = list(access_tokens or [])
        self._api: plaid_api.PlaidApi | None = None

    @property
    def is_configured(self) -> bool:
        return bool(self._client_id and self._secret and self._access_tokens)

    def _get_client(self) -> plaid_api.PlaidApi:
        if self._api is None:
            configuration = plaid.Configuration(
                host=self._environment,
                api_key={"clientId": self._client_id, "secret": self._secret},
            )
            api_client = plaid.ApiClient(configuration)
            self._api = plaid_api.PlaidApi(api_client)
        return self._api

    async def get_accounts(self) -> list[dict[str, Any]]:
        """List all accounts across all linked items (delegates to get_all_balances)."""
        return await self.get_all_balances()

    def _to_dict(self, obj: Any) -> dict:
        """Convert Plaid model to dict."""
        if hasattr(obj, "to_dict"):
            return obj.to_dict()
        return dict(obj) if isinstance(obj, dict) else {}

    async def get_all_balances(self) -> list[dict[str, Any]]:
        """Get balances for all accounts."""
        results: list[dict[str, Any]] = []
        client = self._get_client()
        for access_token in self._access_tokens:
            try:
                req = AccountsBalanceGetRequest(access_token=access_token)

                def _sync_balance(req=req):
                    return client.accounts_balance_get(req, _request_timeout=30)

                resp = await _run_in_executor(_sync_balance)
                d = self._to_dict(resp)
                accts = d.get("accounts", [])
                for a in accts:
                    balances = a.get("balances", {})
                    a["currentBalance"] = balances.get("current") or 0
                    a["availableBalance"] = balances.get("available") or a["currentBalance"]
                    a["_access_token"] = access_token
                    results.append(a)
            except Exception as e:
                logger.warning("Plaid accounts_balance_get failed: %s", e)
        return results

    async def list_transactions(
        self,
        account_id: str,
        limit: int = 10,
        start_date: date | None = None,
        end_date: date | None = None,
    ) -> list[dict[str, Any]]:
        """List transactions for an account. Tries each access_token until account matches."""
        if not start_date:
            start_date = date.today() - timedelta(days=30)
        if not end_date:
            end_date = date.today()
        client = self._get_client()
        for access_token in self._access_tokens:
            try:
                opts = TransactionsGetRequestOptions(
                    account_ids=[account_id], count=limit, offset=0
                )
                req = TransactionsGetRequest(
                    access_token=access_token,
                    start_date=start_date,
                    end_date=end_date,
                    options=opts,
                )

                def _sync_txns(req=req):
                    return client.transactions_get(req)

                resp = await _run_in_executor(_sync_txns)
                d = self._to_dict(resp)
                return d.get("transactions", [])
            except Exception as e:
                if "account" in str(e).lower() or "not found" in str(e).lower():
                    continue
                logger.warning("Plaid transactions_get failed: %s", e)
        return []

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

    @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)
            name = t.get("name", t.get("merchant_name", "Unknown"))
            date_str = (t.get("date") or "")[:10]
            sign = "+" if amt >= 0 else ""
            lines.append(f"  {date_str} | {sign}${amt:,.2f} | {name}")
        return "\n".join(lines)
