#
#   Copyright 2022 - The Android Open Source Project
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

import os
import time

from acts import asserts
from acts import base_test
from acts import utils
from acts import signals
from acts import test_runner
from acts.controllers import adb
from acts.test_decorators import test_tracker_info
from acts_contrib.test_utils.net import net_test_utils as nutils
from acts_contrib.test_utils.tel.tel_test_utils import _check_file_existence
from acts_contrib.test_utils.tel.tel_test_utils import _generate_file_directory_and_file_name
from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
from acts_contrib.test_utils.net.connectivity_const import MULTIPATH_PREFERENCE_NONE as NONE
from acts_contrib.test_utils.net.connectivity_const import MULTIPATH_PREFERENCE_HANDOVER as HANDOVER
from acts_contrib.test_utils.net.connectivity_const import MULTIPATH_PREFERENCE_RELIABILITY as RELIABILITY
from acts_contrib.test_utils.net.connectivity_const import MULTIPATH_PREFERENCE_PERFORMANCE as PERFORMANCE

DOWNLOAD_PATH = "/sdcard/Download/"
RELIABLE = RELIABILITY | HANDOVER
TIMEOUT = 6


class DataCostTest(base_test.BaseTestClass):
    """ Tests for Wifi Tethering """

    def setup_class(self):
        """ Setup devices for tethering and unpack params """

        req_params = ("wifi_network", "download_file")
        self.unpack_userparams(req_params)

        for ad in self.android_devices:
            nutils.verify_lte_data_and_tethering_supported(ad)

        self.tcpdump_pid = None

    def teardown_class(self):
        """ Reset settings to default """
        for ad in self.android_devices:
            sub_id = str(ad.droid.telephonyGetSubscriberId())
            ad.droid.connectivityFactoryResetNetworkPolicies(sub_id)
            ad.droid.connectivitySetDataWarningLimit(sub_id, -1)
            wutils.reset_wifi(ad)

    def teardown_test(self):
        if self.tcpdump_pid:
            nutils.stop_tcpdump(self.dut, self.tcpdump_pid, self.test_name)
            self.tcpdump_pid = None

    def on_fail(self, test_name, begin_time):
        self.dut.take_bug_report(test_name, begin_time)
        dumpsys_info = self.dut.adb.shell("dumpsys netstats --uid")
        self.dut.log.info(dumpsys_info)

    """ Helper functions """

    def _clear_netstats(self, ad):
        """ Clear netstats stored on device

        Args:
            ad: Android device object
        """
        ad.log.info("Clear netstats record.")
        ad.adb.shell("if [ $(ls /data/system/netstats/) ]; then rm /data/system/netstats/*; fi")
        asserts.assert_equal("", ad.adb.shell("ls /data/system/netstats/"),
                             "Fail to clear netstats.")
        ad.reboot()
        time.sleep(30)
        nutils.verify_lte_data_and_tethering_supported(ad)
        self._check_multipath_preference_from_dumpsys(ad)

    def _check_multipath_preference_from_dumpsys(self, ad):
        """ Check cell multipath_preference from dumpsys

        Args:
            ad: Android device object
        """
        try:
            out = ad.adb.shell("dumpsys connectivity | grep budget")
        except TimeoutError:
            ad.log.warning("Fail to get status from dumpsys.")
            out = ""
        ad.log.info("MultipathPolicyTracker: %s" % out)
        if out:
            asserts.assert_true(
                "HANDOVER|RELIABILITY" in out,
                "Cell multipath preference should be HANDOVER|RELIABILITY.")

    def _get_total_data_usage_for_device(self, ad, conn_type, sub_id):
        """ Get total data usage in MB for device

        Args:
            ad: Android device object
            conn_type: MOBILE/WIFI data usage
            sub_id: subscription id

        Returns:
            Data usage in MB
        """
        # end time should be in milli seconds and at least 2 hours more than the
        # actual end time. NetStats:bucket is of size 2 hours and to ensure to
        # get the most recent data usage, end_time should be +2hours
        end_time = int(time.time() * 1000) + 2 * 1000 * 60 * 60
        data_usage = ad.droid.connectivityQuerySummaryForDevice(
            conn_type, sub_id, 0, end_time)
        data_usage /= 1000.0 * 1000.0  # convert data_usage to MB
        self.log.info("Total data usage is: %s" % data_usage)
        return data_usage

    def _check_if_multipath_preference_valid(self, val, exp):
        """ Check if multipath value is same as expected

        Args:
            val: multipath preference for the network
            exp: expected multipath preference value
        """
        if exp == NONE:
            asserts.assert_true(val == exp, "Multipath value should be 0")
        else:
            asserts.assert_true(val >= exp,
                                "Multipath value should be at least %s" % exp)

    def _verify_multipath_preferences(self, ad, wifi_pref, cell_pref,
                                      wifi_network, cell_network):
        """ Verify mutlipath preferences for wifi and cell networks

        Args:
            ad: Android device object
            wifi_pref: Expected multipath value for wifi network
            cell_pref: Expected multipath value for cell network
            wifi_network: Wifi network id on the device
            cell_network: Cell network id on the device
        """
        wifi_multipath = \
            ad.droid.connectivityGetMultipathPreferenceForNetwork(wifi_network)
        cell_multipath = \
            ad.droid.connectivityGetMultipathPreferenceForNetwork(cell_network)
        self.log.info("WiFi multipath preference: %s" % wifi_multipath)
        self.log.info("Cell multipath preference: %s" % cell_multipath)
        self.log.info("Checking multipath preference for wifi")
        self._check_if_multipath_preference_valid(wifi_multipath, wifi_pref)
        self.log.info("Checking multipath preference for cell")
        self._check_if_multipath_preference_valid(cell_multipath, cell_pref)

    """ Test Cases """

    @test_tracker_info(uuid="e86c8108-3e84-4668-bae4-e5d2c8c27910")
    def test_multipath_preference_low_data_limit(self):
        """ Verify multipath preference when mobile data limit is low

        Steps:
            1. DUT has WiFi and LTE data
            2. Set mobile data usage limit to low value
            3. Verify that multipath preference is 0 for cell network
        """
        # set vars
        ad = self.android_devices[0]
        self.dut = ad
        self._clear_netstats(ad)
        utils.sync_device_time(ad)
        self.tcpdump_pid = nutils.start_tcpdump(ad, self.test_name)

        sub_id = str(ad.droid.telephonyGetSubscriberId())
        cell_network = ad.droid.connectivityGetActiveNetwork()
        self.log.info("cell network %s" % cell_network)
        wutils.wifi_connect(ad, self.wifi_network)
        wifi_network = ad.droid.connectivityGetActiveNetwork()
        self.log.info("wifi network %s" % wifi_network)

        # verify mulipath preference values
        self._verify_multipath_preferences(ad, RELIABLE, RELIABLE,
                                           wifi_network, cell_network)

        # set low data limit on mobile data
        total_pre = self._get_total_data_usage_for_device(ad, 0, sub_id)
        self.log.info("Setting data usage limit to %sMB" % (total_pre + 5))
        ad.droid.connectivitySetDataUsageLimit(
            sub_id, int((total_pre + 5) * 1000.0 * 1000.0))
        self.log.info("Setting data warning limit to %sMB" % (total_pre + 5))
        ad.droid.connectivitySetDataWarningLimit(
            sub_id, int((total_pre + 5) * 1000.0 * 1000.0))

        # verify multipath preference values
        curr_time = time.time()
        while time.time() < curr_time + TIMEOUT:
            try:
                self._verify_multipath_preferences(ad, RELIABLE, NONE,
                                                   wifi_network, cell_network)
                return True
            except signals.TestFailure as e:
                self.log.debug("%s" % e)
            time.sleep(1)
        return False

    @test_tracker_info(uuid="a2781411-d880-476a-9f40-2c67e0f97db9")
    def test_multipath_preference_data_download(self):
        """ Verify multipath preference when large file is downloaded

        Steps:
            1. DUT has WiFi and LTE data
            2. WiFi is active network
            3. Download large file over cell network
            4. Verify multipath preference on cell network is 0
        """
        # set vars
        ad = self.android_devices[1]
        self.dut = ad
        self._clear_netstats(ad)
        utils.sync_device_time(ad)
        self.tcpdump_pid = nutils.start_tcpdump(ad, self.test_name)

        cell_network = ad.droid.connectivityGetActiveNetwork()
        self.log.info("cell network %s" % cell_network)
        wutils.wifi_connect(ad, self.wifi_network)
        wifi_network = ad.droid.connectivityGetActiveNetwork()
        self.log.info("wifi network %s" % wifi_network)

        # verify multipath preference for wifi and cell networks
        self._verify_multipath_preferences(ad, RELIABLE, RELIABLE,
                                           wifi_network, cell_network)

        # download file with cell network
        ad.droid.connectivityNetworkOpenConnection(cell_network,
                                                   self.download_file)
        file_folder, file_name = _generate_file_directory_and_file_name(
            self.download_file, DOWNLOAD_PATH)
        file_path = os.path.join(file_folder, file_name)
        self.log.info("File path: %s" % file_path)
        if _check_file_existence(ad, file_path):
            self.log.info("File exists. Removing file %s" % file_name)
            ad.adb.shell("rm -rf %s%s" % (DOWNLOAD_PATH, file_name))

        #  verify multipath preference values
        curr_time = time.time()
        while time.time() < curr_time + TIMEOUT:
            try:
                self._verify_multipath_preferences(ad, RELIABLE, NONE,
                                                   wifi_network, cell_network)
                return True
            except signals.TestFailure as e:
                self.log.debug("%s" % e)
            time.sleep(1)
        return False

    # TODO gmoturu@: Need to add tests that use the mobility rig and test when
    # the WiFi signal is poor and data signal is good.
