"""
Tests for multi-output refunds functionality.

Tests that refunds can be sent to multiple addresses in a single transaction:
- Customer gets refund minus fee
- Shop owner gets the fee portion
"""

import unittest
from unittest.mock import MagicMock, patch
from decimal import Decimal
import uuid

from ..lib.crypto_watcher.crypto_payment_rescue import (
    PaymentRescue,
    RESTOCKING_FEE_PERCENT,
)
from ..models.crypto_payment import CryptoPayment


class TestMultiOutputRefunds(unittest.TestCase):
    """Test multi-output refund functionality."""

    def setUp(self):
        """Set up test fixtures."""
        self.mock_dbsession = MagicMock()
        self.mock_client = MagicMock()
        self.rescue = PaymentRescue(self.mock_dbsession, self.mock_client)

        # Create a mock payment with shop sweep address
        self.payment = MagicMock(spec=CryptoPayment)
        self.payment.id = uuid.uuid4()
        self.payment.coin_type = "DOGE"
        self.payment.account_index = 0
        self.payment.current_confirmations = 10
        self.payment.shop_sweep_to_address = "DShopSweepAddressTest123"
        self.payment.__str__.return_value = f"Payment {self.payment.id}"

    def test_doge_multi_output_refund(self):
        """Test DOGE refund with multi-output (customer + shop)."""
        # Set up refund details
        received_amount = Decimal("100.0")  # 100 DOGE received
        fee_amount = received_amount * RESTOCKING_FEE_PERCENT  # 9 DOGE fee
        refund_amount = received_amount - fee_amount  # 91 DOGE refund

        refund_details = {
            "type": "overpayment",
            "payment_id": self.payment.id,
            "refund_address": "DCustomerRefundAddress123",
            "received_amount": received_amount,
            "refund_amount": refund_amount,
            "fee_amount": fee_amount,
            "reason": "Test overpayment refund",
        }

        # Mock the sendmany call and getbalance
        self.mock_client.sendmany.return_value = "test_tx_hash_123"
        self.mock_client.getbalance.return_value = 100.0  # Sufficient balance
        self.mock_client._call.return_value = ""  # For getaccount

        # Execute refund
        result = self.rescue.execute_refund(refund_details, self.payment)

        # Verify multi-output sendmany was called
        self.mock_client.sendmany.assert_called_once()
        call_args = self.mock_client.sendmany.call_args[0]

        self.assertEqual(call_args[0], "")  # fromaccount

        # Check outputs
        outputs = call_args[1]
        self.assertEqual(len(outputs), 2)  # Two outputs
        self.assertAlmostEqual(outputs["DCustomerRefundAddress123"], 91.0, places=2)
        self.assertAlmostEqual(outputs["DShopSweepAddressTest123"], 9.0, places=2)

        # Verify result
        self.assertTrue(result["success"])
        self.assertEqual(result["tx_hash"], "test_tx_hash_123")
        self.assertEqual(result["fee_charged"], fee_amount)

    def test_xmr_multi_output_refund(self):
        """Test XMR refund with multi-output (customer + shop)."""
        # Set up payment for XMR
        self.payment.coin_type = "XMR"

        # Set up refund details
        received_amount = Decimal("1.0")  # 1 XMR received
        fee_amount = received_amount * RESTOCKING_FEE_PERCENT  # 0.09 XMR fee
        refund_amount = received_amount - fee_amount  # 0.91 XMR refund

        refund_details = {
            "type": "overpayment",
            "payment_id": self.payment.id,
            "refund_address": "4CustomerRefundAddressXMR123",
            "received_amount": received_amount,
            "refund_amount": refund_amount,
            "fee_amount": fee_amount,
            "reason": "Test overpayment refund",
        }

        # Mock the RPC calls
        def mock_call(method, params=None):
            if method == "transfer":
                return {"tx_hash": "xmr_test_tx_hash_456"}
            else:
                raise ValueError(f"Unexpected method: {method}")

        self.mock_client._call.side_effect = mock_call

        # Execute refund
        result = self.rescue.execute_refund(refund_details, self.payment)

        # Find the transfer call
        transfer_call = None
        for call in self.mock_client._call.call_args_list:
            if call[0][0] == "transfer":
                transfer_call = call
                break

        self.assertIsNotNone(transfer_call)
        transfer_params = transfer_call[0][1]

        # Check destinations
        destinations = transfer_params["destinations"]
        self.assertEqual(len(destinations), 2)  # Two destinations

        # Customer refund destination
        self.assertEqual(destinations[0]["address"], "4CustomerRefundAddressXMR123")
        self.assertEqual(
            destinations[0]["amount"], 910000000000
        )  # 0.91 XMR in piconero

        # Shop fee destination
        self.assertEqual(destinations[1]["address"], "DShopSweepAddressTest123")
        self.assertEqual(destinations[1]["amount"], 90000000000)  # 0.09 XMR in piconero

        # Verify result
        self.assertTrue(result["success"])
        self.assertEqual(result["tx_hash"], "xmr_test_tx_hash_456")
        self.assertEqual(result["fee_charged"], fee_amount)

    def test_refund_no_shop_address_fails(self):
        """Test refund fails when shop has no sweep address."""
        # Remove shop sweep address
        self.payment.shop_sweep_to_address = None

        # Set up refund details
        received_amount = Decimal("100.0")
        fee_amount = received_amount * RESTOCKING_FEE_PERCENT
        refund_amount = received_amount - fee_amount

        refund_details = {
            "type": "overpayment",
            "payment_id": self.payment.id,
            "refund_address": "DCustomerRefundAddress123",
            "received_amount": received_amount,
            "refund_amount": refund_amount,
            "fee_amount": fee_amount,
            "reason": "Test overpayment refund",
        }

        # Execute refund
        result = self.rescue.execute_refund(refund_details, self.payment)

        # Verify refund failed
        self.assertFalse(result["success"])
        self.assertIn("Shop sweep address is required", result["error"])

        # Verify no transaction was attempted
        self.mock_client._call.assert_not_called()
        self.mock_client.sendtoaddress.assert_not_called()

    def test_refund_zero_fee_still_multi_output(self):
        """Test multi-output is used even when fee is zero."""
        # Set up refund with zero fee (shouldn't happen but test edge case)
        refund_details = {
            "type": "overpayment",
            "payment_id": self.payment.id,
            "refund_address": "DCustomerRefundAddress123",
            "received_amount": Decimal("100.0"),
            "refund_amount": Decimal("100.0"),
            "fee_amount": Decimal("0"),
            "reason": "Test refund with zero fee",
        }

        # Mock the sendmany call
        self.mock_client.sendmany.return_value = "zero_fee_tx_123"
        self.mock_client.getbalance.return_value = 100.0
        self.mock_client._call.return_value = ""

        # Execute refund
        result = self.rescue.execute_refund(refund_details, self.payment)

        # Verify multi-output sendmany was still used
        self.mock_client.sendmany.assert_called_once()
        call_args = self.mock_client.sendmany.call_args[0]
        outputs = call_args[1]
        # With 0.005 fee buffer, customer gets slightly less
        self.assertAlmostEqual(outputs["DCustomerRefundAddress123"], 99.995, places=3)
        self.assertEqual(outputs["DShopSweepAddressTest123"], 0.0)  # Zero fee

        # Verify result
        self.assertTrue(result["success"])
        self.assertEqual(result["tx_hash"], "zero_fee_tx_123")

    def test_insufficient_confirmations(self):
        """Test refund is delayed when insufficient confirmations."""
        # Set insufficient confirmations
        self.payment.current_confirmations = 1

        refund_details = {
            "type": "overpayment",
            "payment_id": self.payment.id,
            "refund_address": "DCustomerRefundAddress123",
            "received_amount": Decimal("100.0"),
            "refund_amount": Decimal("91.0"),
            "fee_amount": Decimal("9.0"),
            "reason": "Test refund",
        }

        # Execute refund
        result = self.rescue.execute_refund(refund_details, self.payment)

        # Verify refund was delayed
        self.assertFalse(result["success"])
        self.assertIn("confirmations", result["error"])
        self.assertEqual(result["confirmations_needed"], 1)  # Need 1 more confirmation

        # Verify no transaction was attempted
        self.mock_client._call.assert_not_called()
        self.mock_client.sendtoaddress.assert_not_called()


if __name__ == "__main__":
    unittest.main()
