"""SKALE config test"""

import logging
from unittest import mock

import pytest
from web3.auto import w3

from skale import SkaleManager
from skale.types.node import NodeId
from skale.types.schain import SchainName
from skale.types.validator import ValidatorId
from skale.utils.account_tools import send_eth
from skale.utils.contracts_provision.fake_multisig_contract import deploy_fake_multisig_contract
from skale.utils.contracts_provision.main import (
    add_test2_schain_type,
    add_test_permissions,
    create_nodes,
    create_schain,
    link_nodes_to_validator,
    setup_validator,
)
from skale.utils.contracts_provision.utils import generate_random_name, generate_random_node_data
from skale.utils.helper import get_skale_manager_address
from skale.utils.web3_utils import init_web3
from skale.wallets import Web3Wallet
from skale.wallets.web3_wallet import generate_wallet
from tests.constants import ENDPOINT, TEST_ABI_FILEPATH
from tests.helper import init_fair, init_skale, init_skale_allocator

ETH_AMOUNT_PER_NODE = 1
logger = logging.getLogger(__name__)

_last_finished_test_name: str | None = None


@pytest.fixture(scope='session')
def web3():
    """Returns a SKALE Manager instance with provider from config"""
    w3 = init_web3(ENDPOINT)
    return w3


@pytest.fixture(scope='session')
def skale(web3, request):
    """Returns a cached SKALE Manager instance with provider from config"""
    if not hasattr(request.config, '_cached_skale'):
        skale_obj = init_skale(web3)
        add_test_permissions(skale_obj)
        add_test2_schain_type(skale_obj)
        if skale_obj.constants_holder.get_launch_timestamp() != 0:
            skale_obj.constants_holder.set_launch_timestamp(0)
        deploy_fake_multisig_contract(skale_obj.web3, skale_obj.wallet)
        request.config._cached_skale = skale_obj
    return request.config._cached_skale


@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    outcome = yield
    report = outcome.get_result()
    if report.when == 'call' and report.outcome in {'passed', 'failed', 'skipped'}:
        item.config._last_finished_test_name = item.name


@pytest.fixture(scope='session')
def _skale_stats_logger(skale):
    def _log(test_name: str) -> None:
        logger.warning(
            test_name
            + ' '
            + str(skale.stats.http_posts)
            + ' '
            + str(skale.stats.by_method.most_common(10))
        )

    return _log


@pytest.fixture(autouse=True)
def log_prev_test_stats(request, _skale_stats_logger):
    global _last_finished_test_name
    prev_name = getattr(request.config, '_last_finished_test_name', None)
    if prev_name is not None:
        _skale_stats_logger(prev_name)
    _last_finished_test_name = request.node.name


@pytest.fixture(scope='session')
def fair(web3):
    return init_fair(web3)


@pytest.fixture(scope='session')
def validator(skale) -> ValidatorId:
    return setup_validator(skale)


@pytest.fixture(scope='session')
def number_of_nodes():
    return 2


@pytest.fixture
def node_wallets(skale: SkaleManager, number_of_nodes: int):
    wallets = []
    for _ in range(number_of_nodes):
        wallet = generate_wallet(skale.web3)
        send_eth(
            web3=skale.web3,
            wallet=skale.wallet,
            receiver_address=wallet.address,
            amount=ETH_AMOUNT_PER_NODE,
        )
        wallets.append(wallet)
    return wallets


@pytest.fixture
def node_skales(skale, node_wallets):
    return [
        SkaleManager(ENDPOINT, get_skale_manager_address(TEST_ABI_FILEPATH), wallet)
        for wallet in node_wallets
    ]


@pytest.fixture
def nodes(skale: SkaleManager, node_skales, validator: ValidatorId) -> list[NodeId]:
    link_nodes_to_validator(skale, validator, node_skales)
    ids = create_nodes(node_skales)
    return ids


@pytest.fixture
def node(skale: SkaleManager, validator: ValidatorId) -> tuple[NodeId, Web3Wallet]:
    wallet = generate_wallet(skale.web3)
    send_eth(
        web3=skale.web3,
        wallet=skale.wallet,
        receiver_address=wallet.address,
        amount=ETH_AMOUNT_PER_NODE,
    )
    node_skales = (SkaleManager(ENDPOINT, get_skale_manager_address(TEST_ABI_FILEPATH), wallet),)
    link_nodes_to_validator(skale, validator, node_skales)
    ids = create_nodes(node_skales, names=[generate_random_name()])
    node_id = ids[0]
    return node_id, wallet


@pytest.fixture
def fair_active_nodes(fair, node_wallets):
    main_wallet = fair.wallet

    for wallet in node_wallets:
        fair.wallet = wallet
        ip, _, port, _ = generate_random_node_data()
        self_stake_requirement = fair.staking.self_stake_requirement()
        fair.nodes.register_active(ip=ip, port=port, value=self_stake_requirement)

    fair.wallet = main_wallet
    try:
        yield node_wallets
    finally:
        """TODO: Remove the node from the fair instance."""


@pytest.fixture
def fair_passive_nodes(fair, node_wallets):
    main_wallet = fair.wallet

    for wallet in node_wallets:
        fair.wallet = wallet
        ip, _, port, _ = generate_random_node_data()
        fair.nodes.register_passive(ip=ip, port=port)

    fair.wallet = main_wallet
    try:
        yield node_wallets
    finally:
        """TODO: Remove the node from the fair instance."""


@pytest.fixture
def schain(skale: SkaleManager, nodes: list[NodeId]) -> SchainName:
    return create_schain(
        skale,
        schain_type=1,  # test2 should have 1 index
        random_name=True,
    )


@pytest.fixture(autouse=True)
def isolation(web3):
    response = web3.provider.make_request('evm_snapshot', [])
    if 'result' not in response:
        logger.warning('EVM snapshot not supported by the provider')
        yield
        return
    snapshot_id = response['result']
    try:
        yield
    finally:
        web3.provider.make_request('evm_revert', [snapshot_id])


@pytest.fixture(scope='session')
def skale_allocator(web3):
    """Returns a SKALE Allocator instance with provider from config"""
    return init_skale_allocator(web3)


@pytest.fixture
def empty_account():
    return w3.eth.account.create()


@pytest.fixture
def failed_skale(skale):
    tmp_wait, tmp_sign_and_send = skale.wallet.wait, skale.wallet.sign_and_send
    skale.wallet.sign_and_send = mock.Mock(return_value='0x000000000')
    skale.wallet.wait = mock.Mock(return_value={'status': 0, 'error': 'Test error'})
    try:
        yield skale
    finally:
        skale.wallet.sign_and_send = tmp_sign_and_send
        skale.wallet.wait = tmp_wait
