# -*- coding: utf-8 -*-
#
# Electrum - lightweight Bitcoin client
# Copyright (C) 2018 The Electrum developers
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import os
import json
from typing import Sequence, Tuple, Mapping, Type, List, Optional

from .lntransport import LNPeerAddr
from .util import inv_dict, all_subclasses, classproperty
from . import bitcoin


def read_json(filename, default=None):
    path = os.path.join(os.path.dirname(__file__), filename)
    try:
        with open(path, 'r') as f:
            r = json.loads(f.read())
    except Exception:
        if default is None:
            # Sometimes it's better to hard-fail: the file might be missing
            # due to a packaging issue, which might otherwise go unnoticed.
            raise
        r = default
    return r


def create_fallback_node_list(fallback_nodes_dict: dict[str, dict]) -> List[LNPeerAddr]:
    """Take a json dict of fallback nodes like: k:node_id, v:{k:'host', k:'port'} and return LNPeerAddr list"""
    fallback_nodes = []
    for node_id, address in fallback_nodes_dict.items():
        fallback_nodes.append(
            LNPeerAddr(host=address['host'], port=int(address['port']), pubkey=bytes.fromhex(node_id)))
    return fallback_nodes


# BTCQ project URLs. The original fork from spesmilo/electrum is
# acknowledged in NOTICE/LICENCE; runtime user-facing links point at
# btcq.io so crash dialogs and similar don't direct users to upstream
# Electrum's issue tracker (where BTCQ-specific bugs would just confuse
# the upstream maintainers).
GIT_REPO_URL = "https://btcq.io"
GIT_REPO_ISSUES_URL = "https://btcq.io"
RELEASE_NOTES_URL = "https://raw.githubusercontent.com/spesmilo/electrum/refs/heads/master/RELEASE-NOTES"
BIP39_WALLET_FORMATS = read_json('bip39_wallet_formats.json')


class AbstractNet:

    NET_NAME: str
    TESTNET: bool
    WIF_PREFIX: int
    ADDRTYPE_P2PKH: int
    ADDRTYPE_P2SH: int
    SEGWIT_HRP: str
    BOLT11_HRP: str
    GENESIS: str
    BLOCK_HEIGHT_FIRST_LIGHTNING_CHANNELS: int = 0
    BIP44_COIN_TYPE: int
    LN_REALM_BYTE: int
    DEFAULT_PORTS: Mapping[str, str]
    LN_DNS_SEEDS: Sequence[str]
    XPRV_HEADERS: Mapping[str, int]
    XPRV_HEADERS_INV: Mapping[int, str]
    XPUB_HEADERS: Mapping[str, int]
    XPUB_HEADERS_INV: Mapping[int, str]

    @classmethod
    def max_checkpoint(cls) -> int:
        return max(0, len(cls.CHECKPOINTS) * 2016 - 1)

    @classmethod
    def rev_genesis_bytes(cls) -> bytes:
        return bytes.fromhex(cls.GENESIS)[::-1]

    @classmethod
    def set_as_network(cls) -> None:
        global net
        net = cls

    _cached_default_servers = None
    @classproperty
    def DEFAULT_SERVERS(cls) -> Mapping[str, Mapping[str, str]]:
        if cls._cached_default_servers is None:
            default_file = {} if cls.TESTNET else None  # for mainnet we hard-fail if the file is missing.
            cls._cached_default_servers = read_json(os.path.join('chains', cls.NET_NAME, 'servers.json'), default_file)
        return cls._cached_default_servers

    _cached_fallback_lnnodes = None
    @classproperty
    def FALLBACK_LN_NODES(cls) -> Sequence[LNPeerAddr]:
        if cls._cached_fallback_lnnodes is None:
            default_file = {} if cls.TESTNET else None  # for mainnet we hard-fail if the file is missing.
            d = read_json(os.path.join('chains', cls.NET_NAME, 'fallback_lnnodes.json'), default_file)
            cls._cached_fallback_lnnodes = create_fallback_node_list(d)
        return cls._cached_fallback_lnnodes

    _cached_checkpoints = None
    @classproperty
    def CHECKPOINTS(cls) -> Sequence[Tuple[str, int]]:
        if cls._cached_checkpoints is None:
            default_file = [] if cls.TESTNET else None  # for mainnet we hard-fail if the file is missing.
            cls._cached_checkpoints = read_json(os.path.join('chains', cls.NET_NAME, 'checkpoints.json'), default_file)
        return cls._cached_checkpoints

    @classmethod
    def datadir_subdir(cls) -> Optional[str]:
        """The name of the folder in the filesystem.
        None means top-level, used by mainnet.
        """
        return cls.NET_NAME

    @classmethod
    def cli_flag(cls) -> str:
        """as used in e.g. `$ run_electrum --testnet4`"""
        return cls.NET_NAME

    @classmethod
    def config_key(cls) -> str:
        """as used for SimpleConfig.get()"""
        return cls.NET_NAME



class BtcqMainnet(AbstractNet):

    NET_NAME = "btcq-mainnet"
    TESTNET = False
    # Per daemon CMainParams (btcq-beta finalized 2026-04-24).
    # Mainnet base58 prefixes: P2PKH 0x1A -> 'B', P2SH 0x3A -> 'Q', WIF 0x9A -> 'P'.
    WIF_PREFIX = 0x9A
    ADDRTYPE_P2PKH = 0x1A
    ADDRTYPE_P2SH = 0x3A
    SEGWIT_HRP = "btcq"
    BOLT11_HRP = SEGWIT_HRP
    # Daemon mainnet genesis nNonce + nTime are placeholders pending
    # launch-day mining (daemon's chainparams.cpp keeps the assert lines
    # commented out). Wallet matches that "TBD" posture with empty
    # string until launch-day genesis is mined.
    GENESIS = ""
    DEFAULT_PORTS = {'t': '50001', 's': '50002'}
    # SLIP-0044 coin_type 2827 (T9 keypad encoding of BTCQ). Replaces
    # Bitcoin's inherited 0' to avoid HD-seed-sharing collisions.
    BIP44_COIN_TYPE = 2827
    LN_REALM_BYTE = 0  # TODO: BTCQ-specific value when LN is ported (currently MLDSA_UNSUPPORTED)
    LN_DNS_SEEDS = []

    # Only 'standard' XPRV/XPUB headers — BTCQ uses all-hardened ML-DSA-65
    # derivation; segwit-style yprv/zprv/Yprv/Zprv rows don't apply.
    # Daemon EXT_SECRET_KEY = {0x09, 0xC5, 0x85, 0x19} (Qprv leading char)
    XPRV_HEADERS = {
        'standard':    0x09C58519,  # Qprv
    }
    XPRV_HEADERS_INV = inv_dict(XPRV_HEADERS)
    # Daemon EXT_PUBLIC_KEY = {0x09, 0xC5, 0x9A, 0x73} (Qpub leading char)
    XPUB_HEADERS = {
        'standard':    0x09C59A73,  # Qpub
    }
    XPUB_HEADERS_INV = inv_dict(XPUB_HEADERS)

    _cached_default_servers = {}  # no servers yet

    @classproperty
    def DEFAULT_SERVERS(cls) -> Mapping[str, Mapping[str, str]]:
        return {}

    @classproperty
    def FALLBACK_LN_NODES(cls):
        return []

    @classproperty
    def CHECKPOINTS(cls):
        return []

    @classmethod
    def datadir_subdir(cls):
        return "btcq"

    @classmethod
    def cli_flag(cls) -> str:
        return "btcq"

    @classmethod
    def config_key(cls) -> str:
        return "btcq"


class BtcqTestnet(AbstractNet):

    NET_NAME = "btcq-testnet"
    TESTNET = True
    # Per daemon CTestNetParams (btcq-beta finalized 2026-04-24).
    # Testnet base58 prefixes: P2PKH 0x55 -> 'b', P2SH 0x78 -> 'q', WIF 0xE1 -> 'a'.
    WIF_PREFIX = 0xE1
    ADDRTYPE_P2PKH = 0x55
    ADDRTYPE_P2SH = 0x78
    SEGWIT_HRP = "btcqt"
    BOLT11_HRP = SEGWIT_HRP
    # Per daemon CTestNetParams::genesis. Bumped to the v2 hard-fork
    # (BTCQ-015 uniform SHA3-256 merkle, mined 2026-04-29) genesis —
    # the btcq.5 release flipped the SPV merkle algorithm in
    # verifier.py but missed updating this constant, leaving the
    # wallet stuck with GracefulDisconnect("server on different
    # chain ...") on every server connection.
    GENESIS = "fcd9a20fc86d9316f80996bd9e05d0a2817511e13e6d5aab7e651bb37c95750b"
    DEFAULT_PORTS = {'t': '50001', 's': '50002'}
    BIP44_COIN_TYPE = 1
    LN_REALM_BYTE = 1  # TODO: BTCQ-specific value when LN is ported (currently MLDSA_UNSUPPORTED)
    LN_DNS_SEEDS = []

    # Only 'standard' headers — see BtcqMainnet for rationale.
    # Daemon EXT_SECRET_KEY = {0x14, 0x06, 0x63, 0xA7} (qprv leading char)
    XPRV_HEADERS = {
        'standard':    0x140663A7,  # qprv
    }
    XPRV_HEADERS_INV = inv_dict(XPRV_HEADERS)
    # Daemon EXT_PUBLIC_KEY = {0x14, 0x06, 0x79, 0x01} (qpub leading char)
    XPUB_HEADERS = {
        'standard':    0x14067901,  # qpub
    }
    XPUB_HEADERS_INV = inv_dict(XPUB_HEADERS)

    @classproperty
    def DEFAULT_SERVERS(cls) -> Mapping[str, Mapping[str, str]]:
        # Public testnet electrs on oo.btcq.io. caddy-l4 terminates TLS
        # at port 50002 and forwards to loopback btcq-electrs:50001.
        # Currently firewall-restricted to webtrusted_clients; opens
        # publicly at the public-flip event.
        return {
            "electrum.testnet.btcq.io": {"t": "50001", "s": "50002"},
        }

    @classproperty
    def FALLBACK_LN_NODES(cls):
        return []

    @classproperty
    def CHECKPOINTS(cls):
        return []

    @classmethod
    def cli_flag(cls) -> str:
        return "btcqtestnet"

    @classmethod
    def config_key(cls) -> str:
        return "btcqtestnet"


class BtcqRegtest(BtcqTestnet):

    NET_NAME = "btcq-regtest"
    SEGWIT_HRP = "btcqrt"
    BOLT11_HRP = SEGWIT_HRP
    # Per daemon CRegTestParams::genesis (mined 2026-04-24)
    GENESIS = "3c75fbfdccf783067c26930a6b584bbd466ba123a5be49413e5f2fecd19c8cca"
    LN_DNS_SEEDS = []

    @classproperty
    def DEFAULT_SERVERS(cls) -> Mapping[str, Mapping[str, str]]:
        return {"localhost": {"t": "50001"}}

    @classmethod
    def cli_flag(cls) -> str:
        return "btcqregtest"

    @classmethod
    def config_key(cls) -> str:
        return "btcqregtest"


NETS_LIST = tuple(all_subclasses(AbstractNet))  # type: Sequence[Type[AbstractNet]]
NETS_LIST = tuple(sorted(NETS_LIST, key=lambda x: x.NET_NAME))

assert len(NETS_LIST) == len(set([chain.NET_NAME for chain in NETS_LIST])), "NET_NAME must be unique for each concrete AbstractNet"
assert len(NETS_LIST) == len(set([chain.datadir_subdir() for chain in NETS_LIST])), "datadir must be unique for each concrete AbstractNet"
assert len(NETS_LIST) == len(set([chain.cli_flag() for chain in NETS_LIST])), "cli_flag must be unique for each concrete AbstractNet"
assert len(NETS_LIST) == len(set([chain.config_key() for chain in NETS_LIST])), "config_key must be unique for each concrete AbstractNet"

# don't import net directly, import the module instead (so that net is singleton)
net = BtcqMainnet  # type: Type[AbstractNet]


# ML-DSA-65 feature gates: features not yet supported with ML-DSA-65 keys.
MLDSA_UNSUPPORTED = frozenset({
    'hardware_wallet',
    'multisig',
    'lightning',
    'taproot',
    'xpub_watchonly',
})


def check_mldsa_support(feature: str) -> None:
    if feature in MLDSA_UNSUPPORTED:
        raise NotImplementedError(
            f"{feature} is not yet supported with ML-DSA-65 in Electrum-BTCQ"
        )
