"""Tests for odin_bots.transfers — shared ICRC-1 transfer utilities."""

from unittest.mock import MagicMock, patch

import pytest

from odin_bots.transfers import (
    CKBTC_FEE,
    CKBTC_LEDGER_CANISTER_ID,
    IC_HOST,
    unwrap_canister_result,
    patch_delegate_sender,
    create_icrc1_canister,
    create_ckbtc_minter,
    get_balance,
    transfer,
    get_btc_address,
    check_btc_deposits,
    get_withdrawal_account,
    estimate_withdrawal_fee,
    retrieve_btc_withdrawal,
)


# ---------------------------------------------------------------------------
# unwrap_canister_result
# ---------------------------------------------------------------------------

class TestUnwrapCanisterResult:
    def test_list_with_value_dict(self):
        assert unwrap_canister_result([{"value": 42}]) == 42

    def test_list_with_plain_item(self):
        assert unwrap_canister_result([123]) == 123

    def test_empty_list(self):
        assert unwrap_canister_result([]) == []

    def test_non_list_passthrough(self):
        assert unwrap_canister_result("hello") == "hello"

    def test_dict_passthrough(self):
        result = {"Ok": 5}
        assert unwrap_canister_result(result) == {"Ok": 5}

    def test_none_passthrough(self):
        assert unwrap_canister_result(None) is None

    def test_nested_value(self):
        assert unwrap_canister_result([{"value": {"Ok": 99}}]) == {"Ok": 99}


# ---------------------------------------------------------------------------
# patch_delegate_sender
# ---------------------------------------------------------------------------

class TestPatchDelegateSender:
    def test_patches_sender_method(self):
        mock_identity = MagicMock()
        mock_identity.der_pubkey = b"\x00" * 44

        patch_delegate_sender(mock_identity)

        principal = mock_identity.sender()
        assert principal is not None
        assert str(principal) != ""

    def test_sender_returns_consistent_principal(self):
        mock_identity = MagicMock()
        mock_identity.der_pubkey = b"\xab" * 44

        patch_delegate_sender(mock_identity)

        p1 = mock_identity.sender()
        p2 = mock_identity.sender()
        assert p1 == p2


# ---------------------------------------------------------------------------
# create_icrc1_canister
# ---------------------------------------------------------------------------

class TestCreateIcrc1Canister:
    @patch("odin_bots.transfers.Canister")
    def test_default_canister_id(self, MockCanister):
        agent = MagicMock()
        create_icrc1_canister(agent)
        MockCanister.assert_called_once()
        assert MockCanister.call_args.kwargs["canister_id"] == CKBTC_LEDGER_CANISTER_ID

    @patch("odin_bots.transfers.Canister")
    def test_custom_canister_id(self, MockCanister):
        agent = MagicMock()
        create_icrc1_canister(agent, "custom-id")
        assert MockCanister.call_args.kwargs["canister_id"] == "custom-id"


# ---------------------------------------------------------------------------
# create_ckbtc_minter
# ---------------------------------------------------------------------------

class TestCreateCkbtcMinter:
    @patch("odin_bots.transfers.Canister")
    def test_creates_minter(self, MockCanister):
        agent = MagicMock()
        create_ckbtc_minter(agent)
        MockCanister.assert_called_once()
        assert MockCanister.call_args.kwargs["auto_fetch_candid"] is True


# ---------------------------------------------------------------------------
# get_balance
# ---------------------------------------------------------------------------

class TestGetBalance:
    @patch("odin_bots.transfers.Principal")
    def test_returns_balance(self, MockPrincipal):
        canister = MagicMock()
        canister.icrc1_balance_of.return_value = [{"value": 5000}]
        assert get_balance(canister, "some-principal") == 5000

    @patch("odin_bots.transfers.Principal")
    def test_returns_zero_balance(self, MockPrincipal):
        canister = MagicMock()
        canister.icrc1_balance_of.return_value = [{"value": 0}]
        assert get_balance(canister, "some-principal") == 0


# ---------------------------------------------------------------------------
# transfer
# ---------------------------------------------------------------------------

class TestTransfer:
    @patch("odin_bots.transfers.Principal")
    def test_successful_transfer(self, MockPrincipal):
        canister = MagicMock()
        canister.icrc1_transfer.return_value = [{"value": {"Ok": 123}}]
        result = transfer(canister, "to-principal", 1000)
        assert result == {"Ok": 123}

    @patch("odin_bots.transfers.Principal")
    def test_transfer_error(self, MockPrincipal):
        canister = MagicMock()
        err = {"Err": {"InsufficientFunds": {"balance": 0}}}
        canister.icrc1_transfer.return_value = [{"value": err}]
        result = transfer(canister, "to-principal", 1000)
        assert "Err" in result

    @patch("odin_bots.transfers.Principal")
    def test_transfer_calls_with_correct_amount(self, MockPrincipal):
        canister = MagicMock()
        canister.icrc1_transfer.return_value = [{"value": {"Ok": 1}}]
        transfer(canister, "to-principal", 5000)
        call_args = canister.icrc1_transfer.call_args[0][0]
        assert call_args["amount"] == 5000


# ---------------------------------------------------------------------------
# get_btc_address
# ---------------------------------------------------------------------------

class TestGetBtcAddress:
    @patch("odin_bots.transfers.Principal")
    def test_returns_address(self, MockPrincipal):
        minter = MagicMock()
        minter.get_btc_address.return_value = [{"value": "bc1qtest"}]
        result = get_btc_address(minter, "owner-principal")
        assert result == "bc1qtest"


# ---------------------------------------------------------------------------
# check_btc_deposits
# ---------------------------------------------------------------------------

class TestCheckBtcDeposits:
    @patch("odin_bots.transfers.Principal")
    def test_returns_result(self, MockPrincipal):
        minter = MagicMock()
        minter.update_balance.return_value = [{"value": {"Ok": [{"block_index": 1}]}}]
        result = check_btc_deposits(minter, "owner-principal")
        assert "Ok" in result


# ---------------------------------------------------------------------------
# get_withdrawal_account
# ---------------------------------------------------------------------------

class TestGetWithdrawalAccount:
    def test_returns_account(self):
        minter = MagicMock()
        minter.get_withdrawal_account.return_value = [
            {"value": {"owner": "minter-principal", "subaccount": []}}
        ]
        result = get_withdrawal_account(minter)
        assert result["owner"] == "minter-principal"


# ---------------------------------------------------------------------------
# estimate_withdrawal_fee
# ---------------------------------------------------------------------------

class TestEstimateWithdrawalFee:
    def test_returns_fee(self):
        minter = MagicMock()
        minter.estimate_withdrawal_fee.return_value = [
            {"value": {"minter_fee": 10, "bitcoin_fee": 2000}}
        ]
        result = estimate_withdrawal_fee(minter)
        assert result["minter_fee"] == 10
        assert result["bitcoin_fee"] == 2000


# ---------------------------------------------------------------------------
# retrieve_btc_withdrawal
# ---------------------------------------------------------------------------

class TestRetrieveBtcWithdrawal:
    def test_returns_result(self):
        minter = MagicMock()
        minter.retrieve_btc.return_value = [
            {"value": {"Ok": {"block_index": 42}}}
        ]
        result = retrieve_btc_withdrawal(minter, "bc1qtest", 5000)
        assert result == {"Ok": {"block_index": 42}}


# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------

class TestConstants:
    def test_ckbtc_fee(self):
        assert CKBTC_FEE == 10

    def test_ic_host(self):
        assert IC_HOST == "https://ic0.app"

    def test_ckbtc_canister_id(self):
        assert CKBTC_LEDGER_CANISTER_ID == "mxzaz-hqaaa-aaaar-qaada-cai"
