import logging

from django.test import TestCase

from eth_account import Account

from gnosis.eth.constants import NULL_ADDRESS
from gnosis.eth.contracts import get_safe_contract
from gnosis.eth.utils import get_eth_address_with_key

from ..safe_creation_tx import SafeCreationTx
from .safe_test_case import SafeTestCaseMixin
from .utils import generate_valid_s

logger = logging.getLogger(__name__)

LOG_TITLE_WIDTH = 100


class TestSafeCreationTx(SafeTestCaseMixin, TestCase):
    def test_safe_creation_tx_builder(self):
        logger.info(
            "Test Safe Proxy creation without payment".center(LOG_TITLE_WIDTH, "-")
        )
        w3 = self.w3

        s = generate_valid_s()

        funder_account = self.ethereum_test_account
        owners = [get_eth_address_with_key()[0] for _ in range(4)]
        threshold = len(owners) - 1
        gas_price = self.gas_price

        safe_creation_tx = SafeCreationTx(
            w3=w3,
            owners=owners,
            threshold=threshold,
            signature_s=s,
            master_copy=self.safe_contract_V0_0_1_address,
            gas_price=gas_price,
            funder=NULL_ADDRESS,
        )

        logger.info(
            "Send %d gwei to deployer %s",
            w3.fromWei(safe_creation_tx.payment_ether, "gwei"),
            safe_creation_tx.deployer_address,
        )

        self.send_tx(
            {
                "to": safe_creation_tx.deployer_address,
                "value": safe_creation_tx.payment_ether,
            },
            funder_account,
        )

        logger.info(
            "Create proxy contract with address %s", safe_creation_tx.safe_address
        )

        tx_hash = w3.eth.send_raw_transaction(safe_creation_tx.tx_raw)
        tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
        self.assertEqual(tx_receipt.contractAddress, safe_creation_tx.safe_address)

        deployed_safe_proxy_contract = get_safe_contract(w3, tx_receipt.contractAddress)

        logger.info(
            "Deployer account has still %d gwei left (will be lost)",
            w3.fromWei(w3.eth.get_balance(safe_creation_tx.deployer_address), "gwei"),
        )

        self.assertEqual(
            deployed_safe_proxy_contract.functions.getThreshold().call(), threshold
        )
        self.assertEqual(
            deployed_safe_proxy_contract.functions.getOwners().call(), owners
        )

    def test_safe_creation_tx_builder_with_not_enough_funds(self):
        w3 = self.w3
        s = generate_valid_s()
        funder_account = self.ethereum_test_account
        owners = [get_eth_address_with_key()[0] for _ in range(4)]
        threshold = len(owners) - 1
        gas_price = self.gas_price

        safe_creation_tx = SafeCreationTx(
            w3=w3,
            owners=owners,
            threshold=threshold,
            signature_s=s,
            master_copy=self.safe_contract_V0_0_1_address,
            gas_price=gas_price,
            funder=NULL_ADDRESS,
        )

        logger.info(
            "Send %d gwei to deployer %s",
            w3.fromWei(safe_creation_tx.payment_ether - 1, "gwei"),
            safe_creation_tx.deployer_address,
        )
        self.send_tx(
            {
                "to": safe_creation_tx.deployer_address,
                "value": safe_creation_tx.payment_ether - 1,
            },
            funder_account,
        )

        with self.assertRaisesMessage(ValueError, "enough funds"):
            w3.eth.send_raw_transaction(safe_creation_tx.tx_raw)

    def test_safe_creation_tx_builder_with_payment(self):
        logger.info(
            "Test Safe Proxy creation With Payment".center(LOG_TITLE_WIDTH, "-")
        )
        w3 = self.w3

        s = generate_valid_s()

        funder_account = self.ethereum_test_account
        owners = [get_eth_address_with_key()[0] for _ in range(2)]
        threshold = len(owners) - 1
        gas_price = self.gas_price

        safe_creation_tx = SafeCreationTx(
            w3=w3,
            owners=owners,
            threshold=threshold,
            signature_s=s,
            master_copy=self.safe_contract_V0_0_1_address,
            gas_price=gas_price,
            funder=funder_account.address,
        )

        user_external_account = Account.create()
        # Send some ether to that account
        safe_balance = w3.toWei(0.01, "ether")
        self.send_tx(
            {"to": user_external_account.address, "value": safe_balance * 2},
            funder_account,
        )

        logger.info(
            "Send %d ether to safe %s",
            w3.fromWei(safe_balance, "ether"),
            safe_creation_tx.safe_address,
        )
        self.send_tx(
            {"to": safe_creation_tx.safe_address, "value": safe_balance},
            user_external_account,
        )
        self.assertEqual(
            w3.eth.get_balance(safe_creation_tx.safe_address), safe_balance
        )

        logger.info(
            "Send %d gwei to deployer %s",
            w3.fromWei(safe_creation_tx.payment_ether, "gwei"),
            safe_creation_tx.deployer_address,
        )
        self.send_tx(
            {
                "to": safe_creation_tx.deployer_address,
                "value": safe_creation_tx.payment_ether,
            },
            funder_account,
        )

        logger.info(
            "Create proxy contract with address %s", safe_creation_tx.safe_address
        )

        funder_balance = w3.eth.get_balance(funder_account.address)

        # This tx will create the Safe Proxy and return ether to the funder
        tx_hash = w3.eth.send_raw_transaction(safe_creation_tx.tx_raw)
        tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
        self.assertEqual(tx_receipt.contractAddress, safe_creation_tx.safe_address)

        self.assertEqual(
            w3.eth.get_balance(funder_account.address),
            funder_balance + safe_creation_tx.payment,
        )

        logger.info(
            "Deployer account has still %d gwei left (will be lost)",
            w3.fromWei(w3.eth.get_balance(safe_creation_tx.deployer_address), "gwei"),
        )

        deployed_safe_proxy_contract = get_safe_contract(w3, tx_receipt.contractAddress)

        self.assertEqual(
            deployed_safe_proxy_contract.functions.getThreshold().call(), threshold
        )
        self.assertEqual(
            deployed_safe_proxy_contract.functions.getOwners().call(), owners
        )

    def test_safe_creation_tx_builder_with_token_payment(self):
        logger.info(
            "Test Safe Proxy creation With Gas Payment".center(LOG_TITLE_WIDTH, "-")
        )
        w3 = self.w3

        s = generate_valid_s()

        erc20_deployer = Account.create()
        funder_account = self.ethereum_test_account

        # Send something to the erc20 deployer
        self.send_tx(
            {"to": erc20_deployer.address, "value": w3.toWei(1, "ether")},
            funder_account,
        )

        funder = funder_account.address
        owners = [get_eth_address_with_key()[0] for _ in range(2)]
        threshold = len(owners) - 1
        gas_price = self.gas_price
        token_amount = int(1e18)
        erc20_contract = self.deploy_example_erc20(token_amount, erc20_deployer.address)
        self.assertEqual(
            erc20_contract.functions.balanceOf(erc20_deployer.address).call(),
            token_amount,
        )

        safe_creation_tx = SafeCreationTx(
            w3=w3,
            owners=owners,
            threshold=threshold,
            signature_s=s,
            master_copy=self.safe_contract_V0_0_1_address,
            gas_price=gas_price,
            payment_token=erc20_contract.address,
            funder=funder,
        )

        # In this test we will pretend that ether value = token value, so we send tokens as ether payment
        payment = safe_creation_tx.payment
        deployer_address = safe_creation_tx.deployer_address
        safe_address = safe_creation_tx.safe_address
        logger.info("Send %d tokens to safe %s", payment, safe_address)
        self.send_tx(
            erc20_contract.functions.transfer(safe_address, payment).buildTransaction(
                {"from": erc20_deployer.address}
            ),
            erc20_deployer,
        )
        self.assertEqual(
            erc20_contract.functions.balanceOf(safe_address).call(), payment
        )

        logger.info(
            "Send %d ether to deployer %s",
            w3.fromWei(payment, "ether"),
            deployer_address,
        )
        self.send_tx(
            {
                "to": safe_creation_tx.deployer_address,
                "value": safe_creation_tx.payment,
            },
            funder_account,
        )

        logger.info(
            "Create proxy contract with address %s", safe_creation_tx.safe_address
        )
        funder_balance = w3.eth.get_balance(funder)

        # This tx will create the Safe Proxy and return tokens to the funder
        tx_hash = w3.eth.send_raw_transaction(safe_creation_tx.tx_raw)
        tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
        self.assertEqual(tx_receipt.contractAddress, safe_address)
        self.assertEqual(w3.eth.get_balance(funder), funder_balance)
        self.assertEqual(erc20_contract.functions.balanceOf(funder).call(), payment)
        self.assertEqual(erc20_contract.functions.balanceOf(safe_address).call(), 0)

        logger.info(
            "Deployer account has still %d gwei left (will be lost)",
            w3.fromWei(w3.eth.get_balance(safe_creation_tx.deployer_address), "gwei"),
        )

        deployed_safe_proxy_contract = get_safe_contract(w3, tx_receipt.contractAddress)

        self.assertEqual(
            deployed_safe_proxy_contract.functions.getThreshold().call(), threshold
        )
        self.assertEqual(
            deployed_safe_proxy_contract.functions.getOwners().call(), owners
        )

        # Payment should be <= when payment_token_eth_value is 1.0
        # Funder will already have tokens so no storage need to be paid)
        safe_creation_tx_2 = SafeCreationTx(
            w3=w3,
            owners=owners,
            threshold=threshold,
            signature_s=s,
            master_copy=self.safe_contract_V0_0_1_address,
            gas_price=gas_price,
            payment_token=erc20_contract.address,
            payment_token_eth_value=1.0,
            funder=funder,
        )
        self.assertLessEqual(safe_creation_tx_2.payment, safe_creation_tx.payment)

        # Now payment should be equal when payment_token_eth_value is 1.0
        safe_creation_tx_3 = SafeCreationTx(
            w3=w3,
            owners=owners,
            threshold=threshold,
            signature_s=s,
            master_copy=self.safe_contract_V0_0_1_address,
            gas_price=gas_price,
            payment_token=erc20_contract.address,
            payment_token_eth_value=1.0,
            funder=funder,
        )
        self.assertEqual(safe_creation_tx_3.payment, safe_creation_tx_2.payment)

        # Check that payment is less when payment_token_eth_value is set(token value > ether)
        safe_creation_tx_4 = SafeCreationTx(
            w3=w3,
            owners=owners,
            threshold=threshold,
            signature_s=s,
            master_copy=self.safe_contract_V0_0_1_address,
            gas_price=gas_price,
            payment_token=erc20_contract.address,
            payment_token_eth_value=1.1,
            funder=funder,
        )
        self.assertLess(safe_creation_tx_4.payment, safe_creation_tx.payment)

        # Check that payment is more when payment_token_eth_value is set(token value < ether)
        safe_creation_tx_5 = SafeCreationTx(
            w3=w3,
            owners=owners,
            threshold=threshold,
            signature_s=s,
            master_copy=self.safe_contract_V0_0_1_address,
            gas_price=gas_price,
            payment_token=erc20_contract.address,
            payment_token_eth_value=0.1,
            funder=funder,
        )
        self.assertGreater(safe_creation_tx_5.payment, safe_creation_tx.payment)

    def test_safe_creation_tx_builder_with_fixed_cost(self):
        logger.info(
            "Test Safe Proxy creation With Fixed Cost".center(LOG_TITLE_WIDTH, "-")
        )
        w3 = self.w3

        s = generate_valid_s()

        funder_account = self.ethereum_test_account
        owners = [get_eth_address_with_key()[0] for _ in range(2)]
        threshold = len(owners) - 1
        gas_price = self.gas_price
        fixed_creation_cost = 123  # Wei

        safe_creation_tx = SafeCreationTx(
            w3=w3,
            owners=owners,
            threshold=threshold,
            signature_s=s,
            master_copy=self.safe_contract_V0_0_1_address,
            gas_price=gas_price,
            payment_token=None,
            funder=funder_account.address,
            fixed_creation_cost=fixed_creation_cost,
        )

        self.assertEqual(safe_creation_tx.payment, fixed_creation_cost)
        self.assertEqual(
            safe_creation_tx.payment_ether,
            safe_creation_tx.gas * safe_creation_tx.gas_price,
        )

        deployer_address = safe_creation_tx.deployer_address
        safe_address = safe_creation_tx.safe_address
        safe_balance = w3.toWei(0.01, "ether")
        logger.info(
            "Send %d ether to safe %s", w3.fromWei(safe_balance, "ether"), safe_address
        )
        self.send_tx({"to": safe_address, "value": safe_balance}, funder_account)
        self.assertEqual(w3.eth.get_balance(safe_address), safe_balance)

        logger.info(
            "Send %d ether to deployer %s",
            w3.fromWei(safe_creation_tx.payment_ether, "ether"),
            deployer_address,
        )
        self.send_tx(
            {"to": deployer_address, "value": safe_creation_tx.payment_ether},
            funder_account,
        )

        logger.info(
            "Create proxy contract with address %s", safe_creation_tx.safe_address
        )

        funder_balance = w3.eth.get_balance(funder_account.address)

        # This tx will create the Safe Proxy and return tokens to the funder
        tx_hash = w3.eth.send_raw_transaction(safe_creation_tx.tx_raw)
        tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
        self.assertEqual(tx_receipt.contractAddress, safe_address)
        self.assertEqual(
            w3.eth.get_balance(safe_address), safe_balance - fixed_creation_cost
        )
        self.assertLess(
            w3.eth.get_balance(deployer_address), safe_creation_tx.payment_ether
        )
        self.assertEqual(
            w3.eth.get_balance(funder_account.address),
            funder_balance + safe_creation_tx.payment,
        )

        logger.info(
            "Deployer account has still %d gwei left (will be lost)",
            w3.fromWei(w3.eth.get_balance(safe_creation_tx.deployer_address), "gwei"),
        )

        deployed_safe_proxy_contract = get_safe_contract(w3, safe_address)

        self.assertEqual(
            deployed_safe_proxy_contract.functions.getThreshold().call(), threshold
        )
        self.assertEqual(
            deployed_safe_proxy_contract.functions.getOwners().call(), owners
        )

    def test_safe_gas_with_multiple_owners(self):
        logger.info(
            "Test Safe Proxy creation gas with multiple owners".center(
                LOG_TITLE_WIDTH, "-"
            )
        )
        w3 = self.w3
        funder_account = self.ethereum_test_account
        number_of_accounts = 10
        for i in range(2, number_of_accounts):
            s = generate_valid_s()
            owners = [get_eth_address_with_key()[0] for _ in range(i + 1)]
            threshold = len(owners)
            gas_price = w3.toWei(15, "gwei")

            safe_creation_tx = SafeCreationTx(
                w3=w3,
                owners=owners,
                threshold=threshold,
                signature_s=s,
                master_copy=self.safe_contract_V0_0_1_address,
                gas_price=gas_price,
                funder=None,
            )

            self.send_tx(
                {
                    "to": safe_creation_tx.deployer_address,
                    "value": safe_creation_tx.payment,
                },
                funder_account,
            )
            tx_hash = w3.eth.send_raw_transaction(safe_creation_tx.tx_raw)
            tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
            self.assertEqual(tx_receipt.contractAddress, safe_creation_tx.safe_address)

            logger.info(
                "Number of owners: %d - Gas estimated %d - Gas Used %d - Difference %d",
                len(owners),
                safe_creation_tx.gas,
                tx_receipt.gasUsed,
                safe_creation_tx.gas - tx_receipt.gasUsed,
            )
