import base64

try:
    import breez_sdk  # type: ignore

    BREEZ_SDK_INSTALLED = True
except ImportError:
    BREEZ_SDK_INSTALLED = False

if not BREEZ_SDK_INSTALLED:

    class BreezSdkWallet:  # pyright: ignore
        def __init__(self):
            raise RuntimeError(
                "Breez SDK is not installed. "
                "Ask admin to run `poetry install -E breez` to install it."
            )

else:
    import asyncio
    from pathlib import Path
    from typing import AsyncGenerator, Optional

    from loguru import logger

    from lnbits import bolt11 as lnbits_bolt11
    from lnbits.settings import settings

    from .base import (
        InvoiceResponse,
        PaymentFailedStatus,
        PaymentPendingStatus,
        PaymentResponse,
        PaymentStatus,
        PaymentSuccessStatus,
        StatusResponse,
        UnsupportedError,
        Wallet,
    )

    breez_event_queue: asyncio.Queue = asyncio.Queue()

    def load_bytes(source: str, extension: str) -> Optional[bytes]:
        # first check if it can be read from a file
        if source.split(".")[-1] == extension:
            with open(source, "rb") as f:
                source_bytes = f.read()
                return source_bytes
        else:
            # else check the source string can be converted from hex
            try:
                return bytes.fromhex(source)
            except ValueError:
                pass
            # else convert from base64
            try:
                return base64.b64decode(source)
            except Exception:
                pass
        return None

    def load_greenlight_credentials() -> (
        Optional[
            breez_sdk.GreenlightCredentials  # pyright: ignore[reportUnboundVariable]
        ]
    ):
        if (
            settings.breez_greenlight_device_key
            and settings.breez_greenlight_device_cert
        ):
            device_key_bytes = load_bytes(settings.breez_greenlight_device_key, "pem")
            device_cert_bytes = load_bytes(settings.breez_greenlight_device_cert, "crt")
            if not device_key_bytes or not device_cert_bytes:
                raise ValueError(
                    "cannot initialize BreezSdkWallet: "
                    "cannot decode breez_greenlight_device_key "
                    "or breez_greenlight_device_cert"
                )
            return breez_sdk.GreenlightCredentials(  # pyright: ignore[reportUnboundVariable]
                developer_key=list(device_key_bytes),
                developer_cert=list(device_cert_bytes),
            )
        return None

    class SDKListener(
        breez_sdk.EventListener  # pyright: ignore[reportUnboundVariable]
    ):
        def on_event(self, event):
            logger.debug(event)
            breez_event_queue.put_nowait(event)

    class BreezSdkWallet(Wallet):  # type: ignore[no-redef]
        def __init__(self):
            if not settings.breez_greenlight_seed:
                raise ValueError(
                    "cannot initialize BreezSdkWallet: missing breez_greenlight_seed"
                )
            if not settings.breez_api_key:
                raise ValueError(
                    "cannot initialize BreezSdkWallet: missing breez_api_key"
                )
            if (
                settings.breez_greenlight_device_key
                and not settings.breez_greenlight_device_cert
            ):
                raise ValueError(
                    "cannot initialize BreezSdkWallet: "
                    "missing breez_greenlight_device_cert"
                )
            if (
                settings.breez_greenlight_device_cert
                and not settings.breez_greenlight_device_key
            ):
                raise ValueError(
                    "cannot initialize BreezSdkWallet: "
                    "missing breez_greenlight_device_key"
                )

            self.config = breez_sdk.default_config(
                breez_sdk.EnvironmentType.PRODUCTION,
                settings.breez_api_key,
                breez_sdk.NodeConfig.GREENLIGHT(
                    config=breez_sdk.GreenlightNodeConfig(
                        partner_credentials=load_greenlight_credentials(),
                        invite_code=settings.breez_greenlight_invite_code,
                    )
                ),
            )

            breez_sdk_working_dir = Path(settings.lnbits_data_folder, "breez-sdk")
            breez_sdk_working_dir.mkdir(parents=True, exist_ok=True)
            self.config.working_dir = breez_sdk_working_dir.absolute().as_posix()

            try:
                seed = breez_sdk.mnemonic_to_seed(settings.breez_greenlight_seed)
                connect_request = breez_sdk.ConnectRequest(self.config, seed)
                self.sdk_services = breez_sdk.connect(connect_request, SDKListener())
            except Exception as exc:
                logger.warning(exc)
                raise ValueError(f"cannot initialize BreezSdkWallet: {exc!s}") from exc

        async def cleanup(self):
            self.sdk_services.disconnect()

        async def status(self) -> StatusResponse:
            try:
                node_info: breez_sdk.NodeState = self.sdk_services.node_info()
            except Exception as exc:
                return StatusResponse(f"Failed to connect to breez, got: '{exc}...'", 0)

            return StatusResponse(None, int(node_info.channels_balance_msat))

        async def create_invoice(
            self,
            amount: int,
            memo: Optional[str] = None,
            description_hash: Optional[bytes] = None,
            unhashed_description: Optional[bytes] = None,
            **kwargs,
        ) -> InvoiceResponse:
            # if description_hash or unhashed_description:
            #     raise UnsupportedError("description_hash and unhashed_description")
            try:
                if description_hash and not unhashed_description:
                    raise UnsupportedError(
                        "'description_hash' unsupported by Greenlight, provide"
                        " 'unhashed_description'"
                    )
                breez_invoice: breez_sdk.ReceivePaymentResponse = (
                    self.sdk_services.receive_payment(
                        breez_sdk.ReceivePaymentRequest(
                            amount * 1000,  # breez uses msat
                            (
                                unhashed_description.decode()
                                if unhashed_description
                                else memo
                            ),
                            preimage=kwargs.get("preimage"),
                            opening_fee_params=None,
                            use_description_hash=True if unhashed_description else None,
                        )
                    )
                )

                return InvoiceResponse(
                    True,
                    breez_invoice.ln_invoice.payment_hash,
                    breez_invoice.ln_invoice.bolt11,
                    None,
                )
            except Exception as e:
                logger.warning(e)
                return InvoiceResponse(False, None, None, str(e))

        async def pay_invoice(
            self, bolt11: str, fee_limit_msat: int
        ) -> PaymentResponse:
            invoice = lnbits_bolt11.decode(bolt11)

            try:
                send_payment_request = breez_sdk.SendPaymentRequest(bolt11=bolt11)
                send_payment_response: breez_sdk.SendPaymentResponse = (
                    self.sdk_services.send_payment(send_payment_request)
                )
                payment: breez_sdk.Payment = send_payment_response.payment
            except Exception as exc:
                logger.warning(exc)
                try:
                    # try to report issue to Breez to improve LSP routing
                    self.sdk_services.report_issue(
                        breez_sdk.ReportIssueRequest.PAYMENT_FAILURE(
                            breez_sdk.ReportPaymentFailureDetails(invoice.payment_hash)
                        )
                    )
                except Exception as ex:
                    logger.info(ex)
                # assume that payment failed?
                return PaymentResponse(
                    False, None, None, None, f"payment failed: {exc}"
                )

            if payment.status != breez_sdk.PaymentStatus.COMPLETE:
                return PaymentResponse(False, None, None, None, "payment is pending")

            # let's use the payment_hash as the checking_id
            checking_id = invoice.payment_hash

            return PaymentResponse(
                True,
                checking_id,
                payment.fee_msat,
                payment.details.data.payment_preimage,
                None,
            )

        async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
            try:
                payment: breez_sdk.Payment = self.sdk_services.payment_by_hash(
                    checking_id
                )
                if payment is None:
                    return PaymentPendingStatus()
                if payment.payment_type != breez_sdk.PaymentType.RECEIVED:
                    logger.warning(f"unexpected payment type: {payment.status}")
                    return PaymentPendingStatus()
                if payment.status == breez_sdk.PaymentStatus.FAILED:
                    return PaymentFailedStatus()
                if payment.status == breez_sdk.PaymentStatus.COMPLETE:
                    return PaymentSuccessStatus()
                return PaymentPendingStatus()
            except Exception as exc:
                logger.warning(exc)
                return PaymentPendingStatus()

        async def get_payment_status(self, checking_id: str) -> PaymentStatus:
            try:
                payment: breez_sdk.Payment = self.sdk_services.payment_by_hash(
                    checking_id
                )
                if payment is None:
                    return PaymentPendingStatus()
                if payment.payment_type != breez_sdk.PaymentType.SENT:
                    logger.warning(f"unexpected payment type: {payment.status}")
                    return PaymentPendingStatus()
                if payment.status == breez_sdk.PaymentStatus.COMPLETE:
                    return PaymentSuccessStatus(
                        fee_msat=payment.fee_msat,
                        preimage=payment.details.data.payment_preimage,
                    )
                if payment.status == breez_sdk.PaymentStatus.FAILED:
                    return PaymentFailedStatus()
                return PaymentPendingStatus()
            except Exception as exc:
                logger.warning(exc)
                return PaymentPendingStatus()

        async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
            while True:
                event = await breez_event_queue.get()
                if event.is_invoice_paid():
                    yield event.details.payment_hash
