"""SocialValue — the main wallet class for efficient micropayments."""

from __future__ import annotations

import logging
import os
from typing import TYPE_CHECKING

from .config import SocialValueConfig
from .types import (
    BatchTransferItem,
    FeeEstimate,
    PaymentRecord,
    SecretStr,
    TransferResult,
    WalletState,
    _validate_amount_sat,
    _validate_destination,
)

if TYPE_CHECKING:
    from .backend import Backend

logger = logging.getLogger(__name__)

# Fee estimates (sats) — approximate, actual fees come from the SDK at runtime
_FEE_ESTIMATES = {
    "liquid": {
        "swap_in_base": 47,
        "swap_in_pct": 0.0025,
        "swap_out_base": 34,
        "swap_out_pct": 0.001,
        "internal": 1,
    },
    "spark": {
        "swap_in_base": 10,
        "swap_in_pct": 0.001,
        "swap_out_base": 10,
        "swap_out_pct": 0.001,
        "internal": 0,
    },
}

MAX_LIST_LIMIT = 1000


class SocialValueError(Exception):
    """Sanitized error from social-value operations. Never contains secrets."""


def _create_backend(backend_name: str) -> Backend:
    """Create a backend instance by name."""
    if backend_name == "liquid":
        from .backend_liquid import LiquidBackend
        return LiquidBackend()
    elif backend_name == "spark":
        from .backend_spark import SparkBackend
        return SparkBackend()
    else:
        raise ValueError(f"Unknown backend: {backend_name}")


class SocialValue:
    """Economic intelligence for sovereign entities.

    Creates its own wallet (Liquid or Spark) for efficient micropayments.
    Lightning passports in/out. Internal transfers at near-zero fees.

    Choose your backend:
        - "liquid" — Breez SDK Liquid (production, ~47 sat swap fees)
        - "spark"  — Breez SDK Spark (near-zero fees, newer)

    Usage:
        config = SocialValueConfig(breez_api_key=SecretStr("..."), backend="liquid")
        wallet = SocialValue(config=config, mnemonic=SecretStr("..."))
        wallet.connect()

        wallet.deposit(amount_sat=5000)
        wallet.transfer(destination="...", amount_sat=100, memo="payout")
        wallet.withdraw(destination="...", amount_sat=4000)

        wallet.disconnect()
    """

    def __init__(
        self,
        config: SocialValueConfig | None = None,
        mnemonic: SecretStr | str | None = None,
    ) -> None:
        self._config = config or SocialValueConfig.from_env()

        # Wrap raw string mnemonic in SecretStr
        raw_mnemonic = mnemonic or os.environ.get("SOCIAL_VALUE_MNEMONIC", "")
        if isinstance(raw_mnemonic, str):
            self._mnemonic = SecretStr(raw_mnemonic)
        else:
            self._mnemonic = raw_mnemonic

        self._backend: Backend | None = None
        self._connected = False

    def __repr__(self) -> str:
        return (
            f"SocialValue(backend='{self._config.backend}', "
            f"connected={self._connected}, mnemonic=SecretStr('***'))"
        )

    @property
    def connected(self) -> bool:
        """Whether the wallet is connected."""
        return self._connected

    @property
    def backend_name(self) -> str:
        """Which backend is configured ('liquid' or 'spark')."""
        return self._config.backend

    def connect(self) -> None:
        """Connect to the configured backend.

        Creates/loads the wallet from the BIP39 mnemonic.
        Must be called before any payment operations.
        """
        if self._connected:
            return

        if not self._mnemonic:
            raise SocialValueError(
                "Mnemonic is required. Set SOCIAL_VALUE_MNEMONIC env var "
                "or pass mnemonic to constructor."
            )

        self._backend = _create_backend(self._config.backend)

        # Extract mnemonic, pass to backend, then clear our reference
        mnemonic_value = self._mnemonic.get_secret_value()
        try:
            self._backend.connect(
                mnemonic=mnemonic_value,
                api_key=self._config.breez_api_key.get_secret_value(),
                network=self._config.network,
                working_dir=self._config.working_dir,
            )
        except Exception:
            raise SocialValueError(
                f"Failed to connect to {self._config.backend} backend. "
                "Check your mnemonic and API key."
            )
        finally:
            # Clear mnemonic from this scope — SDK has its own copy now
            mnemonic_value = ""  # noqa: F841

        # Clear our mnemonic reference — the SDK holds its own copy
        self._mnemonic = SecretStr("")
        self._connected = True
        logger.info("social-value: connected to %s backend", self._config.backend)

    def disconnect(self) -> None:
        """Disconnect and clean up resources."""
        if self._backend and self._connected:
            self._backend.disconnect()
            self._backend = None
            self._connected = False
            logger.info("social-value: disconnected")

    def __del__(self) -> None:
        """Ensure disconnect on garbage collection."""
        if self._connected:
            try:
                self.disconnect()
            except Exception:
                pass

    def _require_connected(self) -> None:
        if not self._connected or self._backend is None:
            raise SocialValueError("Not connected. Call wallet.connect() first.")

    # ── Balance ──────────────────────────────────────────────────

    def get_state(self) -> WalletState:
        """Get current wallet state (balance, pending amounts)."""
        self._require_connected()
        return self._backend.get_state()

    @property
    def balance(self) -> int:
        """Current confirmed balance in sats."""
        return self.get_state().balance_sat

    # ── Deposit (Lightning → wallet) ─────────────────────────────

    def deposit(self, amount_sat: int) -> str:
        """Create a Lightning invoice to deposit sats into the wallet.

        Note: max_balance_sat is a soft cap checked at invoice creation time.
        Between creation and payment, balance may change.

        Args:
            amount_sat: Amount to deposit in satoshis (must be positive).

        Returns:
            bolt11 invoice string.
        """
        self._require_connected()
        _validate_amount_sat(amount_sat)

        if self._config.max_balance_sat > 0:
            current = self.get_state()
            if current.balance_sat + amount_sat > self._config.max_balance_sat:
                raise SocialValueError(
                    f"Deposit of {amount_sat} would exceed max balance of "
                    f"{self._config.max_balance_sat} sats "
                    f"(current: {current.balance_sat})"
                )

        try:
            return self._backend.create_invoice(amount_sat, "social-value deposit")
        except Exception:
            raise SocialValueError(f"Failed to create deposit invoice for {amount_sat} sats")

    # ── Transfer (internal or outbound) ──────────────────────────

    def transfer(
        self,
        destination: str,
        amount_sat: int,
        memo: str = "",
    ) -> TransferResult:
        """Send sats to a destination.

        Works with Liquid addresses (near-zero fee), bolt11 invoices
        (Lightning fee), or Lightning addresses (Lightning fee).

        Args:
            destination: Liquid address, bolt11 invoice, or Lightning address.
            amount_sat: Amount to send (must be positive).
            memo: Optional memo (max 280 chars).

        Returns:
            TransferResult with success status and fee info.
        """
        self._require_connected()
        _validate_amount_sat(amount_sat)
        _validate_destination(destination)

        dest_redacted = destination[:8] + "..."

        logger.info(
            "social-value [%s]: transfer %d sats to %s",
            self._config.backend,
            amount_sat,
            dest_redacted,
        )

        try:
            payment_id = self._backend.send(destination, amount_sat, memo or None)
            return TransferResult(
                success=True,
                amount_sat=amount_sat,
                fee_sat=0,
                destination=destination,
                memo=memo,
                payment_id=payment_id,
            )
        except Exception:
            logger.error("social-value: transfer of %d sats failed", amount_sat)
            raise SocialValueError(
                f"Transfer of {amount_sat} sats to {dest_redacted} failed"
            )

    def batch_transfer(
        self,
        items: list[BatchTransferItem],
    ) -> tuple[TransferResult, ...]:
        """Send sats to multiple destinations.

        Each transfer executes sequentially. Failed transfers don't block
        the rest. Check each result's success field.

        Args:
            items: List of BatchTransferItem(destination, amount_sat, memo).

        Returns:
            Tuple of TransferResult, one per item (same order).
        """
        self._require_connected()
        results: list[TransferResult] = []

        for item in items:
            try:
                result = self.transfer(
                    destination=item.destination,
                    amount_sat=item.amount_sat,
                    memo=item.memo,
                )
                results.append(result)
            except SocialValueError:
                results.append(
                    TransferResult(
                        success=False,
                        amount_sat=item.amount_sat,
                        fee_sat=0,
                        destination=item.destination,
                        memo="FAILED: transfer error",
                    )
                )

        return tuple(results)

    # ── Withdraw (wallet → Lightning) ────────────────────────────

    def withdraw(self, destination: str, amount_sat: int | None = None) -> TransferResult:
        """Withdraw sats to a Lightning destination (passport OUT).

        Args:
            destination: bolt11 invoice or Lightning address.
            amount_sat: Amount to withdraw (must be positive). If None, drains all.

        Returns:
            TransferResult.
        """
        self._require_connected()
        _validate_destination(destination)

        dest_redacted = destination[:8] + "..."

        try:
            if amount_sat is None:
                actual_amount, fee, payment_id = self._backend.send_drain(destination)
            else:
                _validate_amount_sat(amount_sat)
                actual_amount = amount_sat
                payment_id = self._backend.send(destination, amount_sat)
                fee = 0
        except Exception:
            logger.error("social-value: withdrawal to %s failed", dest_redacted)
            raise SocialValueError(f"Withdrawal to {dest_redacted} failed")

        return TransferResult(
            success=True,
            amount_sat=actual_amount,
            fee_sat=fee,
            destination=destination,
            payment_id=payment_id,
        )

    # ── Fee Estimation ───────────────────────────────────────────

    def estimate_fees(self, action: str, amount_sat: int) -> FeeEstimate:
        """Estimate fees for an action without executing it.

        Args:
            action: 'deposit', 'transfer_internal', 'transfer_lightning', or 'withdraw'.
            amount_sat: Amount in satoshis (must be positive).

        Returns:
            FeeEstimate with breakdown.
        """
        _validate_amount_sat(amount_sat)

        fees = _FEE_ESTIMATES.get(self._config.backend)
        if fees is None:
            raise SocialValueError(f"No fee estimates for backend: {self._config.backend}")

        if action == "deposit":
            fee = fees["swap_in_base"] + int(amount_sat * fees["swap_in_pct"])
        elif action in ("transfer_internal", "transfer_liquid"):
            fee = fees["internal"]
        elif action in ("transfer_lightning", "withdraw"):
            fee = fees["swap_out_base"] + int(amount_sat * fees["swap_out_pct"])
        else:
            raise ValueError(f"Unknown action: {action}")

        pct = round(fee / amount_sat * 100, 2)

        return FeeEstimate(
            action=action,
            amount_sat=amount_sat,
            fee_sat=fee,
            fee_pct=pct,
        )

    # ── History ──────────────────────────────────────────────────

    def list_payments(
        self,
        limit: int = 50,
        offset: int = 0,
    ) -> tuple[PaymentRecord, ...]:
        """List recent payments.

        Args:
            limit: Max results (1-1000, default 50).
            offset: Skip first N results (>= 0, default 0).
        """
        self._require_connected()

        if not isinstance(limit, int) or limit < 1:
            raise ValueError("limit must be a positive integer")
        if limit > MAX_LIST_LIMIT:
            raise ValueError(f"limit must be <= {MAX_LIST_LIMIT}")
        if not isinstance(offset, int) or offset < 0:
            raise ValueError("offset must be a non-negative integer")

        return self._backend.list_payments(limit, offset)

    # ── Context Manager ──────────────────────────────────────────

    def __enter__(self) -> SocialValue:
        self.connect()
        return self

    def __exit__(self, *args: object) -> None:
        self.disconnect()
