import itertools
import random
from unittest import mock

from twisted.trial import unittest

from lbrynet.p2p.PaymentRateManager import NegotiatedPaymentRateManager, BasePaymentRateManager
from lbrynet.p2p.Strategy import BasicAvailabilityWeightedStrategy
from lbrynet.p2p.Offer import Offer
from tests.mocks\
    import BlobAvailabilityTracker as DummyBlobAvailabilityTracker, mock_conf_settings

MAX_NEGOTIATION_TURNS = 10
random.seed(12345)


def get_random_sample(list_to_sample):
    result = list_to_sample[
             random.randint(1, len(list_to_sample)):random.randint(1, len(list_to_sample))]
    if not result:
        return get_random_sample(list_to_sample)
    return result


def calculate_negotation_turns(client_base, host_base, host_is_generous=True,
                               client_is_generous=True):
    blobs = [
        'b2e48bb4c88cf46b76adf0d47a72389fae0cd1f19ed27dc5'
        '09138c99509a25423a4cef788d571dca7988e1dca69e6fa0',
        'd7c82e6cac093b3f16107d2ae2b2c75424f1fcad2c7fbdbe'
        '66e4a13c0b6bd27b67b3a29c403b82279ab0f7c1c48d6787',
        '5a450b416275da4bdff604ee7b58eaedc7913c5005b7184f'
        'c3bc5ef0b1add00613587f54217c91097fc039ed9eace9dd',
        'f99d24cd50d4bfd77c2598bfbeeb8415bf0feef21200bdf0'
        'b8fbbde7751a77b7a2c68e09c25465a2f40fba8eecb0b4e0',
        '9dbda74a472a2e5861a5d18197aeba0f5de67c67e401124c'
        '243d2f0f41edf01d7a26aeb0b5fc9bf47f6361e0f0968e2c',
        '91dc64cf1ff42e20d627b033ad5e4c3a4a96856ed8a6e3fb'
        '4cd5fa1cfba4bf72eefd325f579db92f45f4355550ace8e7',
        '6d8017aba362e5c5d0046625a039513419810a0397d72831'
        '8c328a5cc5d96efb589fbca0728e54fe5adbf87e9545ee07',
        '6af95cd062b4a179576997ef1054c9d2120f8592eea045e9'
        '667bea411d520262cd5a47b137eabb7a7871f5f8a79c92dd',
        '8c70d5e2f5c3a6085006198e5192d157a125d92e73787944'
        '72007a61947992768926513fc10924785bdb1761df3c37e6',
        'c84aa1fd8f5009f7c4e71e444e40d95610abc1480834f835'
        'eefb267287aeb10025880a3ce22580db8c6d92efb5bc0c9c'
    ]

    host = mock.Mock()
    host.host = "1.2.3.4"
    client = mock.Mock()
    client.host = "1.2.3.5"

    client_base_prm = BasePaymentRateManager(client_base)
    client_prm = NegotiatedPaymentRateManager(client_base_prm,
                                              DummyBlobAvailabilityTracker(),
                                              generous=client_is_generous)
    host_base_prm = BasePaymentRateManager(host_base)
    host_prm = NegotiatedPaymentRateManager(host_base_prm,
                                            DummyBlobAvailabilityTracker(),
                                            generous=host_is_generous)
    blobs_to_query = get_random_sample(blobs)
    accepted = False
    turns = 0
    while not accepted:
        rate = client_prm.get_rate_blob_data(host, blobs_to_query)
        offer = Offer(rate)
        accepted = host_prm.accept_rate_blob_data(client, blobs_to_query, offer)
        turns += 1
    return turns


class AvailabilityWeightedStrategyTests(unittest.TestCase):
    def setUp(self):
        mock_conf_settings(self)

    def test_first_offer_is_zero_and_second_is_not_if_offer_not_accepted(self):
        strategy = BasicAvailabilityWeightedStrategy(DummyBlobAvailabilityTracker())
        peer = "1.1.1.1"

        blobs = strategy.price_model.blob_tracker.availability.keys()
        offer1 = strategy.make_offer(peer, blobs)

        offer2 = strategy.make_offer(peer, blobs)

        self.assertEqual(offer1.rate, 0.0)
        self.assertNotEqual(offer2.rate, 0.0)

    def test_accept_zero_and_persist_if_accepted(self):
        host_strategy = BasicAvailabilityWeightedStrategy(DummyBlobAvailabilityTracker())
        client_strategy = BasicAvailabilityWeightedStrategy(DummyBlobAvailabilityTracker())

        client = "1.1.1.1"
        host = "1.1.1.2"
        blobs = host_strategy.price_model.blob_tracker.availability.keys()

        offer = client_strategy.make_offer(host, blobs)
        response1 = host_strategy.respond_to_offer(offer, client, blobs)
        client_strategy.update_accepted_offers(host, response1)

        offer = client_strategy.make_offer(host, blobs)
        response2 = host_strategy.respond_to_offer(offer, client, blobs)
        client_strategy.update_accepted_offers(host, response2)

        self.assertFalse(response1.is_too_low)
        self.assertTrue(response1.is_accepted)
        self.assertEqual(response1.rate, 0.0)

        self.assertFalse(response2.is_too_low)
        self.assertTrue(response2.is_accepted)
        self.assertEqual(response2.rate, 0.0)

    def test_how_many_turns_before_accept_with_similar_rate_settings(self):
        base_rates = [0.0001 * n for n in range(1, 10)]
        for host_base, client_base in itertools.product(base_rates, base_rates):
            turns = calculate_negotation_turns(host_base,
                                               client_base,
                                               client_is_generous=False,
                                               host_is_generous=False)
            self.assertGreater(MAX_NEGOTIATION_TURNS, turns)

    def test_generous_connects_in_one_turn(self):
        base_rates = [0.0001 * n for n in range(1, 10)]
        for host_base, client_base in itertools.product(base_rates, base_rates):
            turns = calculate_negotation_turns(host_base, client_base)
            self.assertEqual(1, turns)

    def test_how_many_turns_with_generous_client(self):
        base_rates = [0.0001 * n for n in range(1, 10)]
        for host_base, client_base in itertools.product(base_rates, base_rates):
            turns = calculate_negotation_turns(host_base,
                                               client_base,
                                               host_is_generous=False)
            self.assertGreater(MAX_NEGOTIATION_TURNS, turns)

    def test_how_many_turns_with_generous_host(self):
        base_rates = [0.0001 * n for n in range(1, 10)]
        for host_base, client_base in itertools.product(base_rates, base_rates):
            turns = calculate_negotation_turns(host_base,
                                               client_base,
                                               client_is_generous=False)
            self.assertGreater(MAX_NEGOTIATION_TURNS, turns)